From 481ddd574fcf194bd395ff7a869ea5d274b098f0 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 30 Jul 2025 01:18:51 +0200 Subject: [PATCH 01/45] Add melos setup and check only set up Dart for now --- .github/workflows/dart.yml | 8 +- dart/AUTHORS | 8 - dart/CHANGELOG.md | 1 - dart/LICENSE | 27 - dart/PATENTS | 17 - dart/README.md | 194 - dart/analysis_options.yaml | 25 - dart/class-diagram.svg | 3364 ----------------- dart/dartdoc_options.yaml | 3 - dart/example/.gitignore | 6 - dart/example/README.md | 14 - dart/example/analysis_options.yaml | 5 - dart/example/bin/event_example.dart | 77 - dart/example/bin/example.dart | 110 - dart/example/pubspec.yaml | 15 - dart/example_web/.gitignore | 9 - dart/example_web/README.md | 8 - dart/example_web/analysis_options.yaml | 5 - dart/example_web/pubspec.yaml | 18 - dart/example_web/web/event.dart | 76 - dart/example_web/web/favicon.ico | Bin 3559 -> 0 bytes dart/example_web/web/index.html | 69 - dart/example_web/web/main.dart | 150 - dart/example_web/web/styles.css | 14 - dart/lib/sentry.dart | 63 - dart/lib/sentry_io.dart | 6 - .../lib/src/client_reports/client_report.dart | 30 - .../client_report_recorder.dart | 55 - .../src/client_reports/discard_reason.dart | 15 - .../src/client_reports/discarded_event.dart | 75 - .../noop_client_report_recorder.dart | 20 - dart/lib/src/constants.dart | 30 - .../src/dart_exception_type_identifier.dart | 42 - .../dart_exception_type_identifier_io.dart | 14 - .../dart_exception_type_identifier_web.dart | 6 - dart/lib/src/diagnostic_log.dart | 34 - .../_io_environment_variables.dart | 24 - .../_web_environment_variables.dart | 26 - .../environment/environment_variables.dart | 43 - dart/lib/src/environment/keys.dart | 6 - dart/lib/src/event_processor.dart | 14 - .../deduplication_event_processor.dart | 70 - .../enricher/enricher_event_processor.dart | 9 - .../enricher/flutter_runtime.dart | 76 - .../enricher/io_enricher_event_processor.dart | 142 - .../enricher/io_platform_memory.dart | 143 - .../web_enricher_event_processor.dart | 122 - .../exception/exception_event_processor.dart | 9 - .../exception_group_event_processor.dart | 68 - .../io_exception_event_processor.dart | 127 - .../web_exception_event_processor.dart | 12 - .../event_processor/run_event_processors.dart | 79 - dart/lib/src/exception_cause.dart | 8 - dart/lib/src/exception_cause_extractor.dart | 35 - .../src/exception_stacktrace_extractor.dart | 35 - dart/lib/src/exception_type_identifier.dart | 54 - dart/lib/src/feature_flags_integration.dart | 45 - dart/lib/src/hint.dart | 120 - .../src/http_client/breadcrumb_client.dart | 110 - dart/lib/src/http_client/client_provider.dart | 16 - .../http_client/failed_request_client.dart | 254 -- .../src/http_client/io_client_provider.dart | 67 - .../src/http_client/sentry_http_client.dart | 160 - .../http_client/sentry_http_client_error.dart | 7 - dart/lib/src/http_client/tracing_client.dart | 80 - dart/lib/src/hub.dart | 784 ---- dart/lib/src/hub_adapter.dart | 219 -- dart/lib/src/integration.dart | 14 - ...invalid_sentry_trace_header_exception.dart | 7 - dart/lib/src/isolate_error_integration.dart | 25 - .../load_dart_debug_images_integration.dart | 171 - dart/lib/src/logs_enricher_integration.dart | 37 - dart/lib/src/noop_client.dart | 62 - dart/lib/src/noop_hub.dart | 159 - .../src/noop_isolate_error_integration.dart | 15 - dart/lib/src/noop_log_batcher.dart | 12 - dart/lib/src/noop_sentry_client.dart | 79 - dart/lib/src/noop_sentry_span.dart | 103 - dart/lib/src/origin.dart | 1 - dart/lib/src/origin_io.dart | 2 - dart/lib/src/origin_web.dart | 4 - dart/lib/src/performance_collector.dart | 13 - dart/lib/src/platform/_io_platform.dart | 33 - dart/lib/src/platform/_web_platform.dart | 45 - dart/lib/src/platform/mock_platform.dart | 52 - dart/lib/src/platform/platform.dart | 30 - dart/lib/src/profiling.dart | 22 - dart/lib/src/propagation_context.dart | 57 - dart/lib/src/protocol.dart | 46 - dart/lib/src/protocol/access_aware_map.dart | 53 - dart/lib/src/protocol/breadcrumb.dart | 222 -- dart/lib/src/protocol/contexts.dart | 349 -- dart/lib/src/protocol/debug_image.dart | 148 - dart/lib/src/protocol/debug_meta.dart | 69 - dart/lib/src/protocol/dsn.dart | 68 - dart/lib/src/protocol/max_body_size.dart | 73 - dart/lib/src/protocol/mechanism.dart | 178 - dart/lib/src/protocol/sdk_info.dart | 60 - dart/lib/src/protocol/sdk_version.dart | 131 - dart/lib/src/protocol/sentry_app.dart | 148 - .../src/protocol/sentry_baggage_header.dart | 18 - dart/lib/src/protocol/sentry_browser.dart | 60 - dart/lib/src/protocol/sentry_culture.dart | 89 - dart/lib/src/protocol/sentry_device.dart | 401 -- dart/lib/src/protocol/sentry_event.dart | 420 -- dart/lib/src/protocol/sentry_exception.dart | 112 - .../lib/src/protocol/sentry_feature_flag.dart | 51 - .../src/protocol/sentry_feature_flags.dart | 53 - dart/lib/src/protocol/sentry_feedback.dart | 82 - dart/lib/src/protocol/sentry_geo.dart | 29 - dart/lib/src/protocol/sentry_gpu.dart | 202 - dart/lib/src/protocol/sentry_id.dart | 40 - dart/lib/src/protocol/sentry_level.dart | 57 - dart/lib/src/protocol/sentry_log.dart | 35 - .../src/protocol/sentry_log_attribute.dart | 30 - dart/lib/src/protocol/sentry_log_level.dart | 28 - dart/lib/src/protocol/sentry_message.dart | 70 - .../src/protocol/sentry_operating_system.dart | 130 - dart/lib/src/protocol/sentry_package.dart | 48 - dart/lib/src/protocol/sentry_proxy.dart | 63 - dart/lib/src/protocol/sentry_request.dart | 178 - dart/lib/src/protocol/sentry_response.dart | 106 - dart/lib/src/protocol/sentry_runtime.dart | 105 - dart/lib/src/protocol/sentry_span.dart | 244 -- dart/lib/src/protocol/sentry_stack_frame.dart | 237 -- dart/lib/src/protocol/sentry_stack_trace.dart | 105 - dart/lib/src/protocol/sentry_thread.dart | 82 - .../src/protocol/sentry_trace_context.dart | 128 - .../lib/src/protocol/sentry_trace_header.dart | 51 - dart/lib/src/protocol/sentry_transaction.dart | 155 - .../src/protocol/sentry_transaction_info.dart | 37 - .../sentry_transaction_name_source.dart | 19 - dart/lib/src/protocol/sentry_user.dart | 164 - .../src/protocol/sentry_view_hierarchy.dart | 20 - .../sentry_view_hierarchy_element.dart | 55 - dart/lib/src/protocol/span_id.dart | 38 - dart/lib/src/protocol/span_status.dart | 145 - .../recursive_exception_cause_extractor.dart | 54 - .../src/run_zoned_guarded_integration.dart | 40 - dart/lib/src/runtime_checker.dart | 51 - dart/lib/src/scope.dart | 474 --- dart/lib/src/scope_observer.dart | 16 - dart/lib/src/sdk_lifecycle_hooks.dart | 73 - dart/lib/src/sentry.dart | 438 --- .../io_sentry_attachment.dart | 33 - .../sentry_attachment/sentry_attachment.dart | 128 - dart/lib/src/sentry_baggage.dart | 222 -- dart/lib/src/sentry_client.dart | 688 ---- dart/lib/src/sentry_envelope.dart | 137 - dart/lib/src/sentry_envelope_header.dart | 64 - dart/lib/src/sentry_envelope_item.dart | 86 - dart/lib/src/sentry_envelope_item_header.dart | 33 - dart/lib/src/sentry_event_like.dart | 7 - dart/lib/src/sentry_exception_factory.dart | 87 - dart/lib/src/sentry_isolate.dart | 93 - dart/lib/src/sentry_isolate_extension.dart | 29 - dart/lib/src/sentry_item_type.dart | 10 - dart/lib/src/sentry_log_batcher.dart | 52 - dart/lib/src/sentry_logger.dart | 75 - dart/lib/src/sentry_logger_formatter.dart | 147 - dart/lib/src/sentry_measurement.dart | 76 - dart/lib/src/sentry_measurement_unit.dart | 122 - dart/lib/src/sentry_options.dart | 682 ---- dart/lib/src/sentry_run_zoned_guarded.dart | 118 - dart/lib/src/sentry_sampling_context.dart | 21 - dart/lib/src/sentry_span_context.dart | 69 - dart/lib/src/sentry_span_interface.dart | 90 - dart/lib/src/sentry_stack_trace_factory.dart | 222 -- dart/lib/src/sentry_template_string.dart | 43 - dart/lib/src/sentry_trace_context_header.dart | 117 - dart/lib/src/sentry_trace_origins.dart | 30 - dart/lib/src/sentry_tracer.dart | 438 --- dart/lib/src/sentry_tracer_finish_status.dart | 15 - dart/lib/src/sentry_traces_sampler.dart | 87 - .../src/sentry_traces_sampling_decision.dart | 11 - dart/lib/src/sentry_transaction_context.dart | 81 - dart/lib/src/span_data_convention.dart | 10 - dart/lib/src/spotlight.dart | 12 - dart/lib/src/throwable_mechanism.dart | 20 - dart/lib/src/tracing.dart | 10 - .../transport/client_report_transport.dart | 56 - dart/lib/src/transport/data_category.dart | 38 - dart/lib/src/transport/encode.dart | 14 - dart/lib/src/transport/http_transport.dart | 87 - .../http_transport_request_handler.dart | 98 - dart/lib/src/transport/noop_encode.dart | 7 - dart/lib/src/transport/noop_transport.dart | 11 - dart/lib/src/transport/rate_limit.dart | 11 - dart/lib/src/transport/rate_limit_parser.dart | 95 - dart/lib/src/transport/rate_limiter.dart | 124 - .../transport/spotlight_http_transport.dart | 59 - dart/lib/src/transport/task_queue.dart | 61 - dart/lib/src/transport/transport.dart | 10 - dart/lib/src/type_check_hint.dart | 26 - dart/lib/src/utils.dart | 36 - dart/lib/src/utils/_io_get_isolate_name.dart | 3 - .../_io_get_sentry_operating_system.dart | 72 - dart/lib/src/utils/_web_get_isolate_name.dart | 1 - .../_web_get_sentry_operating_system.dart | 13 - dart/lib/src/utils/breadcrumb_log_level.dart | 16 - dart/lib/src/utils/crc32_utils.dart | 313 -- dart/lib/src/utils/http_header_utils.dart | 16 - dart/lib/src/utils/http_sanitizer.dart | 91 - dart/lib/src/utils/isolate_utils.dart | 7 - dart/lib/src/utils/iterable_utils.dart | 17 - dart/lib/src/utils/os_utils.dart | 14 - dart/lib/src/utils/regex_utils.dart | 9 - dart/lib/src/utils/sample_rate_format.dart | 324 -- dart/lib/src/utils/stacktrace_utils.dart | 18 - dart/lib/src/utils/tracing_utils.dart | 128 - dart/lib/src/utils/transport_utils.dart | 45 - dart/lib/src/utils/url_details.dart | 32 - dart/lib/src/version.dart | 33 - dart/pubspec.yaml | 38 - .../client_report_recorder_test.dart | 94 - .../client_reports/client_report_test.dart | 54 - dart/test/contexts_test.dart | 331 -- dart/test/diagnostic_logger_test.dart | 68 - dart/test/environment_test.dart | 59 - .../deduplication_event_processor_test.dart | 123 - .../enricher/io_enricher_test.dart | 268 -- .../enricher/io_platform_memory_test.dart | 43 - .../enricher/web_enricher_test.dart | 207 - .../exception_group_event_processor_test.dart | 453 --- .../io_exception_event_processor_test.dart | 132 - dart/test/example_web_compile_test.dart | 91 - dart/test/exception_identifier_test.dart | 186 - dart/test/feature_flags_integration_test.dart | 117 - dart/test/hint_test.dart | 108 - .../http_client/breadcrumb_client_test.dart | 301 -- .../failed_request_client_test.dart | 405 -- .../http_client/io_client_provider_test.dart | 267 -- .../http_client/sentry_http_client_test.dart | 201 - .../test/http_client/tracing_client_test.dart | 299 -- dart/test/hub_test.dart | 925 ----- dart/test/initialization_test.dart | 57 - ...ad_dart_debug_images_integration_test.dart | 310 -- dart/test/logs_enricher_interation_test.dart | 99 - dart/test/mocks.dart | 194 - dart/test/mocks.mocks.dart | 115 - .../mocks/mock_client_report_recorder.dart | 25 - dart/test/mocks/mock_envelope.dart | 29 - .../mocks/mock_environment_variables.dart | 33 - dart/test/mocks/mock_hub.dart | 196 - dart/test/mocks/mock_integration.dart | 20 - dart/test/mocks/mock_log_batcher.dart | 19 - dart/test/mocks/mock_runtime_checker.dart | 35 - dart/test/mocks/mock_scope_observer.dart | 79 - dart/test/mocks/mock_sentry_client.dart | 188 - dart/test/mocks/mock_transport.dart | 78 - dart/test/mocks/no_such_method_provider.dart | 7 - dart/test/propagation_context_test.dart | 157 - .../test/protocol/access_aware_map_tests.dart | 118 - dart/test/protocol/breadcrumb_test.dart | 229 -- dart/test/protocol/contexts_test.dart | 127 - dart/test/protocol/culture_test.dart | 55 - dart/test/protocol/debug_image_test.dart | 94 - dart/test/protocol/debug_meta_test.dart | 43 - dart/test/protocol/mechanism_test.dart | 97 - .../test/protocol/rate_limit_parser_test.dart | 146 - dart/test/protocol/rate_limiter_test.dart | 350 -- dart/test/protocol/sdk_info_test.dart | 74 - dart/test/protocol/sdk_version_test.dart | 111 - dart/test/protocol/sentry_app_test.dart | 112 - .../protocol/sentry_baggage_header_test.dart | 43 - dart/test/protocol/sentry_browser_test.dart | 60 - dart/test/protocol/sentry_device_test.dart | 350 -- dart/test/protocol/sentry_exception_test.dart | 158 - .../protocol/sentry_feature_flag_tests.dart | 38 - .../protocol/sentry_feature_flags_tests.dart | 42 - dart/test/protocol/sentry_feedback_test.dart | 85 - dart/test/protocol/sentry_gpu_test.dart | 91 - .../protocol/sentry_log_attribute_test.dart | 42 - dart/test/protocol/sentry_log_test.dart | 86 - dart/test/protocol/sentry_message_test.dart | 68 - .../sentry_operating_system_test.dart | 79 - dart/test/protocol/sentry_package_test.dart | 63 - dart/test/protocol/sentry_proxy_test.dart | 102 - dart/test/protocol/sentry_request_test.dart | 92 - dart/test/protocol/sentry_response_test.dart | 64 - dart/test/protocol/sentry_runtime_test.dart | 71 - .../protocol/sentry_stack_frame_test.dart | 127 - .../protocol/sentry_stack_trace_test.dart | 76 - .../protocol/sentry_trace_header_test.dart | 41 - .../sentry_transaction_info_test.dart | 29 - dart/test/protocol/sentry_user_test.dart | 98 - .../sentry_view_hierarchy_element_test.dart | 65 - .../protocol/sentry_view_hierarchy_test.dart | 52 - dart/test/protocol/span_id_test.dart | 23 - dart/test/protocol/span_status_test.dart | 138 - ...ursive_exception_cause_extractor_test.dart | 189 - dart/test/scope_test.dart | 920 ----- dart/test/sdk_lifecycle_hooks_test.dart | 111 - dart/test/sentry_attachment_io_test.dart | 32 - dart/test/sentry_attachment_test.dart | 192 - dart/test/sentry_baggage_test.dart | 97 - dart/test/sentry_browser_test.dart | 13 - dart/test/sentry_client_lifecycle_test.dart | 195 - .../sentry_client_sdk_lifecycle_test.dart | 195 - dart/test/sentry_client_test.dart | 2833 -------------- dart/test/sentry_envelope_header_test.dart | 44 - .../sentry_envelope_item_header_test.dart | 19 - dart/test/sentry_envelope_item_test.dart | 136 - dart/test/sentry_envelope_test.dart | 276 -- dart/test/sentry_envelope_vm_test.dart | 78 - dart/test/sentry_event_test.dart | 500 --- dart/test/sentry_exception_factory_test.dart | 355 -- dart/test/sentry_id_test.dart | 35 - dart/test/sentry_io_client_test.dart | 15 - dart/test/sentry_isolate_extension_test.dart | 60 - dart/test/sentry_isolate_test.dart | 75 - dart/test/sentry_log_batcher_test.dart | 148 - dart/test/sentry_logger_formatter_test.dart | 307 -- dart/test/sentry_logger_test.dart | 95 - dart/test/sentry_measurement_test.dart | 42 - dart/test/sentry_measurement_unit_test.dart | 130 - dart/test/sentry_options_test.dart | 186 - dart/test/sentry_run_zoned_guarded_test.dart | 123 - dart/test/sentry_span_context_test.dart | 47 - dart/test/sentry_span_test.dart | 316 -- dart/test/sentry_template_string_test.dart | 153 - dart/test/sentry_test.dart | 683 ---- .../sentry_trace_context_header_test.dart | 72 - dart/test/sentry_trace_context_test.dart | 82 - dart/test/sentry_tracer_test.dart | 637 ---- dart/test/sentry_traces_sampler_test.dart | 150 - .../test/sentry_transaction_context_test.dart | 76 - dart/test/sentry_transaction_test.dart | 104 - dart/test/stack_trace_test.dart | 352 -- dart/test/test_utils.dart | 459 --- .../client_report_transport_test.dart | 169 - dart/test/transport/http_transport_test.dart | 306 -- .../spotlight_http_transport_test.dart | 69 - dart/test/transport/tesk_queue_test.dart | 145 - dart/test/unsupported_throwables_test.dart | 92 - dart/test/utils/http_header_utils_test.dart | 18 - dart/test/utils/http_sanitizer_test.dart | 189 - dart/test/utils/regex_utils_test.dart | 24 - dart/test/utils/sample_rate_format_test.dart | 49 - dart/test/utils/tracing_utils_test.dart | 357 -- dart/test/utils/url_details_test.dart | 93 - dart/test/utils_test.dart | 34 - dart/test/version_test.dart | 22 - .../envelope-no-attachment.envelope | 3 - .../envelope-with-image.envelope | Bin 3712 -> 0 bytes dart/test_resources/sentry.png | Bin 3535 -> 0 bytes dart/test_resources/testfile.txt | 1 - dart/tool/presubmit.sh | 19 - melos.yaml | 7 + pubspec.yaml | 9 + 350 files changed, 20 insertions(+), 43416 deletions(-) delete mode 100644 dart/AUTHORS delete mode 120000 dart/CHANGELOG.md delete mode 100644 dart/LICENSE delete mode 100644 dart/PATENTS delete mode 100644 dart/README.md delete mode 100644 dart/analysis_options.yaml delete mode 100644 dart/class-diagram.svg delete mode 100644 dart/dartdoc_options.yaml delete mode 100644 dart/example/.gitignore delete mode 100644 dart/example/README.md delete mode 100644 dart/example/analysis_options.yaml delete mode 100644 dart/example/bin/event_example.dart delete mode 100644 dart/example/bin/example.dart delete mode 100644 dart/example/pubspec.yaml delete mode 100644 dart/example_web/.gitignore delete mode 100644 dart/example_web/README.md delete mode 100644 dart/example_web/analysis_options.yaml delete mode 100644 dart/example_web/pubspec.yaml delete mode 100644 dart/example_web/web/event.dart delete mode 100644 dart/example_web/web/favicon.ico delete mode 100644 dart/example_web/web/index.html delete mode 100644 dart/example_web/web/main.dart delete mode 100644 dart/example_web/web/styles.css delete mode 100644 dart/lib/sentry.dart delete mode 100644 dart/lib/sentry_io.dart delete mode 100644 dart/lib/src/client_reports/client_report.dart delete mode 100644 dart/lib/src/client_reports/client_report_recorder.dart delete mode 100644 dart/lib/src/client_reports/discard_reason.dart delete mode 100644 dart/lib/src/client_reports/discarded_event.dart delete mode 100644 dart/lib/src/client_reports/noop_client_report_recorder.dart delete mode 100644 dart/lib/src/constants.dart delete mode 100644 dart/lib/src/dart_exception_type_identifier.dart delete mode 100644 dart/lib/src/dart_exception_type_identifier_io.dart delete mode 100644 dart/lib/src/dart_exception_type_identifier_web.dart delete mode 100644 dart/lib/src/diagnostic_log.dart delete mode 100644 dart/lib/src/environment/_io_environment_variables.dart delete mode 100644 dart/lib/src/environment/_web_environment_variables.dart delete mode 100644 dart/lib/src/environment/environment_variables.dart delete mode 100644 dart/lib/src/environment/keys.dart delete mode 100644 dart/lib/src/event_processor.dart delete mode 100644 dart/lib/src/event_processor/deduplication_event_processor.dart delete mode 100644 dart/lib/src/event_processor/enricher/enricher_event_processor.dart delete mode 100644 dart/lib/src/event_processor/enricher/flutter_runtime.dart delete mode 100644 dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart delete mode 100644 dart/lib/src/event_processor/enricher/io_platform_memory.dart delete mode 100644 dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart delete mode 100644 dart/lib/src/event_processor/exception/exception_event_processor.dart delete mode 100644 dart/lib/src/event_processor/exception/exception_group_event_processor.dart delete mode 100644 dart/lib/src/event_processor/exception/io_exception_event_processor.dart delete mode 100644 dart/lib/src/event_processor/exception/web_exception_event_processor.dart delete mode 100644 dart/lib/src/event_processor/run_event_processors.dart delete mode 100644 dart/lib/src/exception_cause.dart delete mode 100644 dart/lib/src/exception_cause_extractor.dart delete mode 100644 dart/lib/src/exception_stacktrace_extractor.dart delete mode 100644 dart/lib/src/exception_type_identifier.dart delete mode 100644 dart/lib/src/feature_flags_integration.dart delete mode 100644 dart/lib/src/hint.dart delete mode 100644 dart/lib/src/http_client/breadcrumb_client.dart delete mode 100644 dart/lib/src/http_client/client_provider.dart delete mode 100644 dart/lib/src/http_client/failed_request_client.dart delete mode 100644 dart/lib/src/http_client/io_client_provider.dart delete mode 100644 dart/lib/src/http_client/sentry_http_client.dart delete mode 100644 dart/lib/src/http_client/sentry_http_client_error.dart delete mode 100644 dart/lib/src/http_client/tracing_client.dart delete mode 100644 dart/lib/src/hub.dart delete mode 100644 dart/lib/src/hub_adapter.dart delete mode 100644 dart/lib/src/integration.dart delete mode 100644 dart/lib/src/invalid_sentry_trace_header_exception.dart delete mode 100644 dart/lib/src/isolate_error_integration.dart delete mode 100644 dart/lib/src/load_dart_debug_images_integration.dart delete mode 100644 dart/lib/src/logs_enricher_integration.dart delete mode 100644 dart/lib/src/noop_client.dart delete mode 100644 dart/lib/src/noop_hub.dart delete mode 100644 dart/lib/src/noop_isolate_error_integration.dart delete mode 100644 dart/lib/src/noop_log_batcher.dart delete mode 100644 dart/lib/src/noop_sentry_client.dart delete mode 100644 dart/lib/src/noop_sentry_span.dart delete mode 100644 dart/lib/src/origin.dart delete mode 100644 dart/lib/src/origin_io.dart delete mode 100644 dart/lib/src/origin_web.dart delete mode 100644 dart/lib/src/performance_collector.dart delete mode 100644 dart/lib/src/platform/_io_platform.dart delete mode 100644 dart/lib/src/platform/_web_platform.dart delete mode 100644 dart/lib/src/platform/mock_platform.dart delete mode 100644 dart/lib/src/platform/platform.dart delete mode 100644 dart/lib/src/profiling.dart delete mode 100644 dart/lib/src/propagation_context.dart delete mode 100644 dart/lib/src/protocol.dart delete mode 100644 dart/lib/src/protocol/access_aware_map.dart delete mode 100644 dart/lib/src/protocol/breadcrumb.dart delete mode 100644 dart/lib/src/protocol/contexts.dart delete mode 100644 dart/lib/src/protocol/debug_image.dart delete mode 100644 dart/lib/src/protocol/debug_meta.dart delete mode 100644 dart/lib/src/protocol/dsn.dart delete mode 100644 dart/lib/src/protocol/max_body_size.dart delete mode 100644 dart/lib/src/protocol/mechanism.dart delete mode 100644 dart/lib/src/protocol/sdk_info.dart delete mode 100644 dart/lib/src/protocol/sdk_version.dart delete mode 100644 dart/lib/src/protocol/sentry_app.dart delete mode 100644 dart/lib/src/protocol/sentry_baggage_header.dart delete mode 100644 dart/lib/src/protocol/sentry_browser.dart delete mode 100644 dart/lib/src/protocol/sentry_culture.dart delete mode 100644 dart/lib/src/protocol/sentry_device.dart delete mode 100644 dart/lib/src/protocol/sentry_event.dart delete mode 100644 dart/lib/src/protocol/sentry_exception.dart delete mode 100644 dart/lib/src/protocol/sentry_feature_flag.dart delete mode 100644 dart/lib/src/protocol/sentry_feature_flags.dart delete mode 100644 dart/lib/src/protocol/sentry_feedback.dart delete mode 100644 dart/lib/src/protocol/sentry_geo.dart delete mode 100644 dart/lib/src/protocol/sentry_gpu.dart delete mode 100644 dart/lib/src/protocol/sentry_id.dart delete mode 100644 dart/lib/src/protocol/sentry_level.dart delete mode 100644 dart/lib/src/protocol/sentry_log.dart delete mode 100644 dart/lib/src/protocol/sentry_log_attribute.dart delete mode 100644 dart/lib/src/protocol/sentry_log_level.dart delete mode 100644 dart/lib/src/protocol/sentry_message.dart delete mode 100644 dart/lib/src/protocol/sentry_operating_system.dart delete mode 100644 dart/lib/src/protocol/sentry_package.dart delete mode 100644 dart/lib/src/protocol/sentry_proxy.dart delete mode 100644 dart/lib/src/protocol/sentry_request.dart delete mode 100644 dart/lib/src/protocol/sentry_response.dart delete mode 100644 dart/lib/src/protocol/sentry_runtime.dart delete mode 100644 dart/lib/src/protocol/sentry_span.dart delete mode 100644 dart/lib/src/protocol/sentry_stack_frame.dart delete mode 100644 dart/lib/src/protocol/sentry_stack_trace.dart delete mode 100644 dart/lib/src/protocol/sentry_thread.dart delete mode 100644 dart/lib/src/protocol/sentry_trace_context.dart delete mode 100644 dart/lib/src/protocol/sentry_trace_header.dart delete mode 100644 dart/lib/src/protocol/sentry_transaction.dart delete mode 100644 dart/lib/src/protocol/sentry_transaction_info.dart delete mode 100644 dart/lib/src/protocol/sentry_transaction_name_source.dart delete mode 100644 dart/lib/src/protocol/sentry_user.dart delete mode 100644 dart/lib/src/protocol/sentry_view_hierarchy.dart delete mode 100644 dart/lib/src/protocol/sentry_view_hierarchy_element.dart delete mode 100644 dart/lib/src/protocol/span_id.dart delete mode 100644 dart/lib/src/protocol/span_status.dart delete mode 100644 dart/lib/src/recursive_exception_cause_extractor.dart delete mode 100644 dart/lib/src/run_zoned_guarded_integration.dart delete mode 100644 dart/lib/src/runtime_checker.dart delete mode 100644 dart/lib/src/scope.dart delete mode 100644 dart/lib/src/scope_observer.dart delete mode 100644 dart/lib/src/sdk_lifecycle_hooks.dart delete mode 100644 dart/lib/src/sentry.dart delete mode 100644 dart/lib/src/sentry_attachment/io_sentry_attachment.dart delete mode 100644 dart/lib/src/sentry_attachment/sentry_attachment.dart delete mode 100644 dart/lib/src/sentry_baggage.dart delete mode 100644 dart/lib/src/sentry_client.dart delete mode 100644 dart/lib/src/sentry_envelope.dart delete mode 100644 dart/lib/src/sentry_envelope_header.dart delete mode 100644 dart/lib/src/sentry_envelope_item.dart delete mode 100644 dart/lib/src/sentry_envelope_item_header.dart delete mode 100644 dart/lib/src/sentry_event_like.dart delete mode 100644 dart/lib/src/sentry_exception_factory.dart delete mode 100644 dart/lib/src/sentry_isolate.dart delete mode 100644 dart/lib/src/sentry_isolate_extension.dart delete mode 100644 dart/lib/src/sentry_item_type.dart delete mode 100644 dart/lib/src/sentry_log_batcher.dart delete mode 100644 dart/lib/src/sentry_logger.dart delete mode 100644 dart/lib/src/sentry_logger_formatter.dart delete mode 100644 dart/lib/src/sentry_measurement.dart delete mode 100644 dart/lib/src/sentry_measurement_unit.dart delete mode 100644 dart/lib/src/sentry_options.dart delete mode 100644 dart/lib/src/sentry_run_zoned_guarded.dart delete mode 100644 dart/lib/src/sentry_sampling_context.dart delete mode 100644 dart/lib/src/sentry_span_context.dart delete mode 100644 dart/lib/src/sentry_span_interface.dart delete mode 100644 dart/lib/src/sentry_stack_trace_factory.dart delete mode 100644 dart/lib/src/sentry_template_string.dart delete mode 100644 dart/lib/src/sentry_trace_context_header.dart delete mode 100644 dart/lib/src/sentry_trace_origins.dart delete mode 100644 dart/lib/src/sentry_tracer.dart delete mode 100644 dart/lib/src/sentry_tracer_finish_status.dart delete mode 100644 dart/lib/src/sentry_traces_sampler.dart delete mode 100644 dart/lib/src/sentry_traces_sampling_decision.dart delete mode 100644 dart/lib/src/sentry_transaction_context.dart delete mode 100644 dart/lib/src/span_data_convention.dart delete mode 100644 dart/lib/src/spotlight.dart delete mode 100644 dart/lib/src/throwable_mechanism.dart delete mode 100644 dart/lib/src/tracing.dart delete mode 100644 dart/lib/src/transport/client_report_transport.dart delete mode 100644 dart/lib/src/transport/data_category.dart delete mode 100644 dart/lib/src/transport/encode.dart delete mode 100644 dart/lib/src/transport/http_transport.dart delete mode 100644 dart/lib/src/transport/http_transport_request_handler.dart delete mode 100644 dart/lib/src/transport/noop_encode.dart delete mode 100644 dart/lib/src/transport/noop_transport.dart delete mode 100644 dart/lib/src/transport/rate_limit.dart delete mode 100644 dart/lib/src/transport/rate_limit_parser.dart delete mode 100644 dart/lib/src/transport/rate_limiter.dart delete mode 100644 dart/lib/src/transport/spotlight_http_transport.dart delete mode 100644 dart/lib/src/transport/task_queue.dart delete mode 100644 dart/lib/src/transport/transport.dart delete mode 100644 dart/lib/src/type_check_hint.dart delete mode 100644 dart/lib/src/utils.dart delete mode 100644 dart/lib/src/utils/_io_get_isolate_name.dart delete mode 100644 dart/lib/src/utils/_io_get_sentry_operating_system.dart delete mode 100644 dart/lib/src/utils/_web_get_isolate_name.dart delete mode 100644 dart/lib/src/utils/_web_get_sentry_operating_system.dart delete mode 100644 dart/lib/src/utils/breadcrumb_log_level.dart delete mode 100644 dart/lib/src/utils/crc32_utils.dart delete mode 100644 dart/lib/src/utils/http_header_utils.dart delete mode 100644 dart/lib/src/utils/http_sanitizer.dart delete mode 100644 dart/lib/src/utils/isolate_utils.dart delete mode 100644 dart/lib/src/utils/iterable_utils.dart delete mode 100644 dart/lib/src/utils/os_utils.dart delete mode 100644 dart/lib/src/utils/regex_utils.dart delete mode 100644 dart/lib/src/utils/sample_rate_format.dart delete mode 100644 dart/lib/src/utils/stacktrace_utils.dart delete mode 100644 dart/lib/src/utils/tracing_utils.dart delete mode 100644 dart/lib/src/utils/transport_utils.dart delete mode 100644 dart/lib/src/utils/url_details.dart delete mode 100644 dart/lib/src/version.dart delete mode 100644 dart/pubspec.yaml delete mode 100644 dart/test/client_reports/client_report_recorder_test.dart delete mode 100644 dart/test/client_reports/client_report_test.dart delete mode 100644 dart/test/contexts_test.dart delete mode 100644 dart/test/diagnostic_logger_test.dart delete mode 100644 dart/test/environment_test.dart delete mode 100644 dart/test/event_processor/deduplication_event_processor_test.dart delete mode 100644 dart/test/event_processor/enricher/io_enricher_test.dart delete mode 100644 dart/test/event_processor/enricher/io_platform_memory_test.dart delete mode 100644 dart/test/event_processor/enricher/web_enricher_test.dart delete mode 100644 dart/test/event_processor/exception/exception_group_event_processor_test.dart delete mode 100644 dart/test/event_processor/exception/io_exception_event_processor_test.dart delete mode 100644 dart/test/example_web_compile_test.dart delete mode 100644 dart/test/exception_identifier_test.dart delete mode 100644 dart/test/feature_flags_integration_test.dart delete mode 100644 dart/test/hint_test.dart delete mode 100644 dart/test/http_client/breadcrumb_client_test.dart delete mode 100644 dart/test/http_client/failed_request_client_test.dart delete mode 100644 dart/test/http_client/io_client_provider_test.dart delete mode 100644 dart/test/http_client/sentry_http_client_test.dart delete mode 100644 dart/test/http_client/tracing_client_test.dart delete mode 100644 dart/test/hub_test.dart delete mode 100644 dart/test/initialization_test.dart delete mode 100644 dart/test/load_dart_debug_images_integration_test.dart delete mode 100644 dart/test/logs_enricher_interation_test.dart delete mode 100644 dart/test/mocks.dart delete mode 100644 dart/test/mocks.mocks.dart delete mode 100644 dart/test/mocks/mock_client_report_recorder.dart delete mode 100644 dart/test/mocks/mock_envelope.dart delete mode 100644 dart/test/mocks/mock_environment_variables.dart delete mode 100644 dart/test/mocks/mock_hub.dart delete mode 100644 dart/test/mocks/mock_integration.dart delete mode 100644 dart/test/mocks/mock_log_batcher.dart delete mode 100644 dart/test/mocks/mock_runtime_checker.dart delete mode 100644 dart/test/mocks/mock_scope_observer.dart delete mode 100644 dart/test/mocks/mock_sentry_client.dart delete mode 100644 dart/test/mocks/mock_transport.dart delete mode 100644 dart/test/mocks/no_such_method_provider.dart delete mode 100644 dart/test/propagation_context_test.dart delete mode 100644 dart/test/protocol/access_aware_map_tests.dart delete mode 100644 dart/test/protocol/breadcrumb_test.dart delete mode 100644 dart/test/protocol/contexts_test.dart delete mode 100644 dart/test/protocol/culture_test.dart delete mode 100644 dart/test/protocol/debug_image_test.dart delete mode 100644 dart/test/protocol/debug_meta_test.dart delete mode 100644 dart/test/protocol/mechanism_test.dart delete mode 100644 dart/test/protocol/rate_limit_parser_test.dart delete mode 100644 dart/test/protocol/rate_limiter_test.dart delete mode 100644 dart/test/protocol/sdk_info_test.dart delete mode 100644 dart/test/protocol/sdk_version_test.dart delete mode 100644 dart/test/protocol/sentry_app_test.dart delete mode 100644 dart/test/protocol/sentry_baggage_header_test.dart delete mode 100644 dart/test/protocol/sentry_browser_test.dart delete mode 100644 dart/test/protocol/sentry_device_test.dart delete mode 100644 dart/test/protocol/sentry_exception_test.dart delete mode 100644 dart/test/protocol/sentry_feature_flag_tests.dart delete mode 100644 dart/test/protocol/sentry_feature_flags_tests.dart delete mode 100644 dart/test/protocol/sentry_feedback_test.dart delete mode 100644 dart/test/protocol/sentry_gpu_test.dart delete mode 100644 dart/test/protocol/sentry_log_attribute_test.dart delete mode 100644 dart/test/protocol/sentry_log_test.dart delete mode 100644 dart/test/protocol/sentry_message_test.dart delete mode 100644 dart/test/protocol/sentry_operating_system_test.dart delete mode 100644 dart/test/protocol/sentry_package_test.dart delete mode 100644 dart/test/protocol/sentry_proxy_test.dart delete mode 100644 dart/test/protocol/sentry_request_test.dart delete mode 100644 dart/test/protocol/sentry_response_test.dart delete mode 100644 dart/test/protocol/sentry_runtime_test.dart delete mode 100644 dart/test/protocol/sentry_stack_frame_test.dart delete mode 100644 dart/test/protocol/sentry_stack_trace_test.dart delete mode 100644 dart/test/protocol/sentry_trace_header_test.dart delete mode 100644 dart/test/protocol/sentry_transaction_info_test.dart delete mode 100644 dart/test/protocol/sentry_user_test.dart delete mode 100644 dart/test/protocol/sentry_view_hierarchy_element_test.dart delete mode 100644 dart/test/protocol/sentry_view_hierarchy_test.dart delete mode 100644 dart/test/protocol/span_id_test.dart delete mode 100644 dart/test/protocol/span_status_test.dart delete mode 100644 dart/test/recursive_exception_cause_extractor_test.dart delete mode 100644 dart/test/scope_test.dart delete mode 100644 dart/test/sdk_lifecycle_hooks_test.dart delete mode 100644 dart/test/sentry_attachment_io_test.dart delete mode 100644 dart/test/sentry_attachment_test.dart delete mode 100644 dart/test/sentry_baggage_test.dart delete mode 100644 dart/test/sentry_browser_test.dart delete mode 100644 dart/test/sentry_client_lifecycle_test.dart delete mode 100644 dart/test/sentry_client_sdk_lifecycle_test.dart delete mode 100644 dart/test/sentry_client_test.dart delete mode 100644 dart/test/sentry_envelope_header_test.dart delete mode 100644 dart/test/sentry_envelope_item_header_test.dart delete mode 100644 dart/test/sentry_envelope_item_test.dart delete mode 100644 dart/test/sentry_envelope_test.dart delete mode 100644 dart/test/sentry_envelope_vm_test.dart delete mode 100644 dart/test/sentry_event_test.dart delete mode 100644 dart/test/sentry_exception_factory_test.dart delete mode 100644 dart/test/sentry_id_test.dart delete mode 100644 dart/test/sentry_io_client_test.dart delete mode 100644 dart/test/sentry_isolate_extension_test.dart delete mode 100644 dart/test/sentry_isolate_test.dart delete mode 100644 dart/test/sentry_log_batcher_test.dart delete mode 100644 dart/test/sentry_logger_formatter_test.dart delete mode 100644 dart/test/sentry_logger_test.dart delete mode 100644 dart/test/sentry_measurement_test.dart delete mode 100644 dart/test/sentry_measurement_unit_test.dart delete mode 100644 dart/test/sentry_options_test.dart delete mode 100644 dart/test/sentry_run_zoned_guarded_test.dart delete mode 100644 dart/test/sentry_span_context_test.dart delete mode 100644 dart/test/sentry_span_test.dart delete mode 100644 dart/test/sentry_template_string_test.dart delete mode 100644 dart/test/sentry_test.dart delete mode 100644 dart/test/sentry_trace_context_header_test.dart delete mode 100644 dart/test/sentry_trace_context_test.dart delete mode 100644 dart/test/sentry_tracer_test.dart delete mode 100644 dart/test/sentry_traces_sampler_test.dart delete mode 100644 dart/test/sentry_transaction_context_test.dart delete mode 100644 dart/test/sentry_transaction_test.dart delete mode 100644 dart/test/stack_trace_test.dart delete mode 100644 dart/test/test_utils.dart delete mode 100644 dart/test/transport/client_report_transport_test.dart delete mode 100644 dart/test/transport/http_transport_test.dart delete mode 100644 dart/test/transport/spotlight_http_transport_test.dart delete mode 100644 dart/test/transport/tesk_queue_test.dart delete mode 100644 dart/test/unsupported_throwables_test.dart delete mode 100644 dart/test/utils/http_header_utils_test.dart delete mode 100644 dart/test/utils/http_sanitizer_test.dart delete mode 100644 dart/test/utils/regex_utils_test.dart delete mode 100644 dart/test/utils/sample_rate_format_test.dart delete mode 100644 dart/test/utils/tracing_utils_test.dart delete mode 100644 dart/test/utils/url_details_test.dart delete mode 100644 dart/test/utils_test.dart delete mode 100644 dart/test/version_test.dart delete mode 100644 dart/test_resources/envelope-no-attachment.envelope delete mode 100644 dart/test_resources/envelope-with-image.envelope delete mode 100644 dart/test_resources/sentry.png delete mode 100644 dart/test_resources/testfile.txt delete mode 100755 dart/tool/presubmit.sh create mode 100644 melos.yaml create mode 100644 pubspec.yaml diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index c007b1d4ee..e9ffa6dc21 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -45,18 +45,18 @@ jobs: - uses: ./.github/actions/dart-test with: - directory: dart + directory: packages/dart - uses: ./.github/actions/coverage if: runner.os == 'Linux' && matrix.sdk == 'stable' with: token: ${{ secrets.CODECOV_TOKEN }} - directory: dart + directory: packages/dart coverage: sentry min-coverage: 85 - name: Build example - working-directory: dart/example + working-directory: packages/dart/example run: | dart pub get dart compile aot-snapshot bin/example.dart @@ -64,5 +64,5 @@ jobs: analyze: uses: ./.github/workflows/analyze.yml with: - package: dart + package: packages/dart panaThreshold: 87 diff --git a/dart/AUTHORS b/dart/AUTHORS deleted file mode 100644 index 1739b1535d..0000000000 --- a/dart/AUTHORS +++ /dev/null @@ -1,8 +0,0 @@ -# Below is a list of people and organizations that have contributed -# to package:sentry. Names should be added to the list like so: -# -# Name/Organization - -Google Inc. -Simon Lightfoot -Hadrien Lejard diff --git a/dart/CHANGELOG.md b/dart/CHANGELOG.md deleted file mode 120000 index 04c99a55ca..0000000000 --- a/dart/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../CHANGELOG.md \ No newline at end of file diff --git a/dart/LICENSE b/dart/LICENSE deleted file mode 100644 index 6f2d1444dd..0000000000 --- a/dart/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/dart/PATENTS b/dart/PATENTS deleted file mode 100644 index ac39faf679..0000000000 --- a/dart/PATENTS +++ /dev/null @@ -1,17 +0,0 @@ -Google hereby grants to you a perpetual, worldwide, non-exclusive, -no-charge, royalty-free, irrevocable (except as stated in this -section) patent license to make, have made, use, offer to sell, sell, -import, transfer, and otherwise run, modify and propagate the contents -of this implementation, where such license applies only to those -patent claims, both currently owned by Google and acquired in the -future, licensable by Google that are necessarily infringed by this -implementation. This grant does not include claims that would be -infringed only as a consequence of further modification of this -implementation. If you or your agent or exclusive licensee institute -or order or agree to the institution of patent litigation or any other -patent enforcement activity against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that this -implementation constitutes direct or contributory patent infringement, -or inducement of patent infringement, then any patent rights granted -to you under this License for this implementation shall terminate as -of the date such litigation is filed. \ No newline at end of file diff --git a/dart/README.md b/dart/README.md deleted file mode 100644 index 94e834e9b6..0000000000 --- a/dart/README.md +++ /dev/null @@ -1,194 +0,0 @@ -

- - - -
-

- -Sentry SDK for Dart -=========== - -| package | build | pub | likes | popularity | pub points | -| ------- | ------- | ------- | ------- | ------- | ------- | -| sentry | [![build](https://github.com/getsentry/sentry-dart/workflows/sentry-dart/badge.svg?branch=main)](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Asentry-dart) | [![pub package](https://img.shields.io/pub/v/sentry.svg)](https://pub.dev/packages/sentry) | [![likes](https://img.shields.io/pub/likes/sentry)](https://pub.dev/packages/sentry/score) | [![popularity](https://img.shields.io/pub/popularity/sentry)](https://img.shields.io/pub/sentry) | [![pub points](https://img.shields.io/pub/points/sentry)](https://pub.dev/packages/sentry/score) - -Pure Dart SDK used by any Dart application like AngularDart, CLI and Server. - -#### Flutter - -For Flutter applications there's [`sentry_flutter`](https://pub.dev/packages/sentry_flutter) which builds on top of this package. -That will give you native crash support (for Android and iOS), [release health](https://docs.sentry.io/product/releases/health/), offline caching and more. - -#### Usage - -- Sign up for a Sentry.io account and get a DSN at https://sentry.io. - -- Follow the installing instructions on [pub.dev](https://pub.dev/packages/sentry/install). - -- Initialize the Sentry SDK using the DSN issued by Sentry.io: - -```dart -import 'package:sentry/sentry.dart'; - -Future main() async { - await Sentry.init( - (options) { - options.dsn = 'https://example@sentry.io/example'; - }, - appRunner: initApp, // Init your App. - ); -} - -void initApp() { - // your app code -} -``` - -Or, if you want to run your app in your own error zone [runZonedGuarded]: - -```dart -import 'dart:async'; - -import 'package:sentry/sentry.dart'; - -Future main() async { - runZonedGuarded(() async { - await Sentry.init( - (options) { - options.dsn = 'https://example@sentry.io/example'; - }, - ); - - // Init your App. - initApp(); - }, (exception, stackTrace) async { - await Sentry.captureException(exception, stackTrace: stackTrace); - }); -} - -void initApp() { - // your app code -} -``` - -##### Breadcrumbs for HTTP Requests - -The `SentryHttpClient` can be used as a standalone client like this: -```dart -import 'package:sentry/sentry.dart'; - -var client = SentryHttpClient(); -try { - var uriResponse = await client.post('https://example.com/whatsit/create', - body: {'name': 'doodle', 'color': 'blue'}); - print(await client.get(uriResponse.bodyFields['uri'])); -} finally { - client.close(); -} -``` - -The `SentryHttpClient` can also be used as a wrapper for your own -HTTP [Client](https://pub.dev/documentation/http/latest/http/Client-class.html): -```dart -import 'package:sentry/sentry.dart'; -import 'package:http/http.dart' as http; - -final myClient = http.Client(); - -var client = SentryHttpClient(client: myClient); -try { -var uriResponse = await client.post('https://example.com/whatsit/create', - body: {'name': 'doodle', 'color': 'blue'}); - print(await client.get(uriResponse.bodyFields['uri'])); -} finally { - client.close(); -} -``` - -##### Reporting Bad HTTP Requests as Errors - -The `SentryHttpClient` can also catch exceptions that may occur during requests -such as [`SocketException`](https://api.dart.dev/stable/2.13.4/dart-io/SocketException-class.html)s. -This is currently an opt-in feature. The following example shows how to enable it. - -```dart -import 'package:sentry/sentry.dart'; - -var client = SentryHttpClient(); -try { -var uriResponse = await client.post('https://example.com/whatsit/create', - body: {'name': 'doodle', 'color': 'blue'}); - print(await client.get(uriResponse.bodyFields['uri'])); -} finally { - client.close(); -} -``` - -Furthermore you can track HTTP requests which are considered bad by you. -The following example shows how to do it. It captures exceptions for -each request with a status code range from 400 to 404 and also for 500. - -```dart -import 'package:sentry/sentry.dart'; - -var client = SentryHttpClient( - failedRequestStatusCodes: [ - SentryStatusCode.range(400, 404), - SentryStatusCode(500), - ], -); - -try { -var uriResponse = await client.post('https://example.com/whatsit/create', - body: {'name': 'doodle', 'color': 'blue'}); - print(await client.get(uriResponse.bodyFields['uri'])); -} finally { - client.close(); -} -``` - -##### Performance Monitoring for HTTP Requests - -The `SentryHttpClient` starts a span out of the active span bound to the scope for each HTTP Request. This is currently an opt-in feature. The following example shows how to enable it. - -```dart -import 'package:sentry/sentry.dart'; - -final transaction = Sentry.startTransaction( - 'webrequest', - 'request', - bindToScope: true, -); - -var client = SentryHttpClient(); -try { -var uriResponse = await client.post('https://example.com/whatsit/create', - body: {'name': 'doodle', 'color': 'blue'}); - print(await client.get(uriResponse.bodyFields['uri'])); -} finally { - client.close(); -} - -await transaction.finish(status: SpanStatus.ok()); -``` - -Read more about [Automatic Instrumentation](https://docs.sentry.io/platforms/dart/performance/instrumentation/automatic-instrumentation/). - -##### Tips for catching errors - -- Use a `try/catch` block. -- Use a `catchError` block for `Futures`, examples on [dart.dev](https://dart.dev/guides/libraries/futures-error-handling). -- The SDK already runs your `callback` on an error handler, e.g. using [runZonedGuarded](https://api.flutter.dev/flutter/dart-async/runZonedGuarded.html), events caught by the `runZonedGuarded` are captured automatically. -- [Current Isolate errors](https://api.flutter.dev/flutter/dart-isolate/Isolate/addErrorListener.html) which is the equivalent of a main or UI thread, are captured automatically (Only for non-Web Apps). -- For your own `Isolates`, add an [Error Listener](https://api.flutter.dev/flutter/dart-isolate/Isolate/addErrorListener.html) by calling `isolate.addSentryErrorListener()`. - -#### Resources - -#### Resources - -* [![Flutter docs](https://img.shields.io/badge/documentation-sentry.io-green.svg?label=flutter%20docs)](https://docs.sentry.io/platforms/flutter/) -* [![Dart docs](https://img.shields.io/badge/documentation-sentry.io-green.svg?label=dart%20docs)](https://docs.sentry.io/platforms/dart/) -* [![Discussions](https://img.shields.io/github/discussions/getsentry/sentry-dart.svg)](https://github.com/getsentry/sentry-dart/discussions) -* [![Discord Chat](https://img.shields.io/discord/621778831602221064?logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/gB6ja9uZuN) -* [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](https://stackoverflow.com/questions/tagged/sentry) -* [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](https://twitter.com/intent/follow?screen_name=getsentry) diff --git a/dart/analysis_options.yaml b/dart/analysis_options.yaml deleted file mode 100644 index 74ed00b114..0000000000 --- a/dart/analysis_options.yaml +++ /dev/null @@ -1,25 +0,0 @@ -include: package:lints/recommended.yaml - -analyzer: - exclude: - - example/** # the example has its own 'analysis_options.yaml' - - test/*.mocks.dart - errors: - # treat missing required parameters as a warning (not a hint) - missing_required_param: error - # treat missing returns as a warning (not a hint) - missing_return: error - # allow having TODOs in the code - todo: ignore - # allow self-reference to deprecated members (we do this because otherwise we have - # to annotate every member in every test, assert, etc, when we deprecate something) - deprecated_member_use_from_same_package: warning - -linter: - rules: - prefer_relative_imports: true - unnecessary_brace_in_string_interps: true - prefer_function_declarations_over_variables: false - no_leading_underscores_for_local_identifiers: false - avoid_renaming_method_parameters: false - unawaited_futures: true diff --git a/dart/class-diagram.svg b/dart/class-diagram.svg deleted file mode 100644 index db98a9ad63..0000000000 --- a/dart/class-diagram.svg +++ /dev/null @@ -1,3364 +0,0 @@ - - - - - - - - -cluster~ - -dart - - -cluster~/lib - -lib - - -cluster~/lib/src - -src - - -cluster~/lib/src/environment - -environment - - -cluster~/lib/src/protocol - -protocol - - -cluster~/lib/src/event_processor - -event_processor - - -cluster~/lib/src/event_processor/exception - -exception - - -cluster~/lib/src/event_processor/enricher - -enricher - - -cluster~/lib/src/platform - -platform - - -cluster~/lib/src/sentry_attachment - -sentry_attachment - - -cluster~/lib/src/http_client - -http_client - - -cluster~/lib/src/transport - -transport - - -cluster~/lib/src/utils - -utils - - -cluster~/lib/src/client_reports - -client_reports - - - -/lib/src/noop_hub.dart - -noop_hub - - - -/lib/src/sentry_client.dart - -sentry_client - - - -/lib/src/noop_hub.dart->/lib/src/sentry_client.dart - - - - - -/lib/src/sentry_options.dart - -sentry_options - - - -/lib/src/noop_hub.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/hint.dart - -hint - - - -/lib/src/noop_hub.dart->/lib/src/hint.dart - - - - - -/lib/src/tracing.dart - -tracing - - - -/lib/src/noop_hub.dart->/lib/src/tracing.dart - - - - - -/lib/src/protocol.dart - -protocol - - - -/lib/src/noop_hub.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_user_feedback.dart - -sentry_user_feedback - - - -/lib/src/noop_hub.dart->/lib/src/sentry_user_feedback.dart - - - - - -/lib/src/scope.dart - -scope - - - -/lib/src/noop_hub.dart->/lib/src/scope.dart - - - - - -/lib/src/hub.dart - -hub - - - -/lib/src/noop_hub.dart->/lib/src/hub.dart - - - - - -/lib/src/sentry_event_like.dart - -sentry_event_like - - - -/lib/src/sentry_event_like.dart->/lib/src/protocol.dart - - - - - -/lib/src/version.dart - -version - - - -/lib/src/utils.dart - -utils - - - -/lib/src/sentry_span_context.dart - -sentry_span_context - - - -/lib/sentry.dart - -sentry - - - -/lib/src/sentry_span_context.dart->/lib/sentry.dart - - - - - -/lib/src/environment/_web_environment_variables.dart - -_web_environment_variables - - - -/lib/src/environment/keys.dart - -keys - - - -/lib/src/environment/_web_environment_variables.dart->/lib/src/environment/keys.dart - - - - - -/lib/src/environment/environment_variables.dart - -environment_variables - - - -/lib/src/environment/_web_environment_variables.dart->/lib/src/environment/environment_variables.dart - - - - - -/lib/src/environment/_io_environment_variables.dart - -_io_environment_variables - - - -/lib/src/environment/_io_environment_variables.dart->/lib/src/environment/_web_environment_variables.dart - - - - - -/lib/src/environment/_io_environment_variables.dart->/lib/src/environment/keys.dart - - - - - -/lib/src/environment/_io_environment_variables.dart->/lib/src/environment/environment_variables.dart - - - - - -/lib/src/platform_checker.dart - -platform_checker - - - -/lib/src/environment/environment_variables.dart->/lib/src/platform_checker.dart - - - - - -/lib/src/propagation_context.dart - -propagation_context - - - -/lib/src/sentry_baggage.dart - -sentry_baggage - - - -/lib/src/propagation_context.dart->/lib/src/sentry_baggage.dart - - - - - -/lib/src/propagation_context.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_trace_origins.dart - -sentry_trace_origins - - - -/lib/src/sentry_isolate_extension.dart - -sentry_isolate_extension - - - -/lib/src/hub_adapter.dart - -hub_adapter - - - -/lib/src/sentry_isolate_extension.dart->/lib/src/hub_adapter.dart - - - - - -/lib/src/sentry_isolate.dart - -sentry_isolate - - - -/lib/src/sentry_isolate_extension.dart->/lib/src/sentry_isolate.dart - - - - - -/lib/src/sentry_isolate_extension.dart->/lib/src/hub.dart - - - - - -/lib/src/sentry_item_type.dart - -sentry_item_type - - - -/lib/src/diagnostic_logger.dart - -diagnostic_logger - - - -/lib/src/diagnostic_logger.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/diagnostic_logger.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_traces_sampler.dart - -sentry_traces_sampler - - - -/lib/src/sentry_traces_sampler.dart->/lib/sentry.dart - - - - - -/lib/src/invalid_sentry_trace_header_exception.dart - -invalid_sentry_trace_header_exception - - - -/lib/src/sentry.dart - -sentry - - - -/lib/src/sentry.dart->/lib/src/noop_hub.dart - - - - - -/lib/src/sentry.dart->/lib/src/environment/environment_variables.dart - - - - - -/lib/src/sentry.dart->/lib/src/sentry_client.dart - - - - - -/lib/src/sentry.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/event_processor/deduplication_event_processor.dart - -deduplication_event_processor - - - -/lib/src/sentry.dart->/lib/src/event_processor/deduplication_event_processor.dart - - - - - -/lib/src/event_processor/exception/exception_event_processor.dart - -exception_event_processor - - - -/lib/src/sentry.dart->/lib/src/event_processor/exception/exception_event_processor.dart - - - - - -/lib/src/event_processor/enricher/enricher_event_processor.dart - -enricher_event_processor - - - -/lib/src/sentry.dart->/lib/src/event_processor/enricher/enricher_event_processor.dart - - - - - -/lib/src/sentry.dart->/lib/src/hint.dart - - - - - -/lib/src/sentry.dart->/lib/src/tracing.dart - - - - - -/lib/src/integration.dart - -integration - - - -/lib/src/sentry.dart->/lib/src/integration.dart - - - - - -/lib/src/sentry.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry.dart->/lib/src/hub_adapter.dart - - - - - -/lib/src/sentry.dart->/lib/src/sentry_user_feedback.dart - - - - - -/lib/src/run_zoned_guarded_integration.dart - -run_zoned_guarded_integration - - - -/lib/src/sentry.dart->/lib/src/run_zoned_guarded_integration.dart - - - - - -/lib/src/sentry.dart->/lib/src/hub.dart - - - - - -/lib/src/sentry_envelope_item.dart - -sentry_envelope_item - - - -/lib/src/sentry_envelope_item.dart->/lib/src/utils.dart - - - - - -/lib/src/sentry_envelope_item.dart->/lib/src/sentry_item_type.dart - - - - - -/lib/src/sentry_envelope_item_header.dart - -sentry_envelope_item_header - - - -/lib/src/sentry_envelope_item.dart->/lib/src/sentry_envelope_item_header.dart - - - - - -/lib/src/sentry_envelope_item.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_attachment/sentry_attachment.dart - -sentry_attachment - - - -/lib/src/sentry_envelope_item.dart->/lib/src/sentry_attachment/sentry_attachment.dart - - - - - -/lib/src/sentry_envelope_item.dart->/lib/src/sentry_user_feedback.dart - - - - - -/lib/src/client_reports/client_report.dart - -client_report - - - -/lib/src/sentry_envelope_item.dart->/lib/src/client_reports/client_report.dart - - - - - -/lib/src/sentry_client.dart->/lib/src/version.dart - - - - - -/lib/src/sentry_client.dart->/lib/src/sentry_baggage.dart - - - - - -/lib/src/sentry_client.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/sentry_client.dart->/lib/src/hint.dart - - - - - -/lib/src/sentry_trace_context_header.dart - -sentry_trace_context_header - - - -/lib/src/sentry_client.dart->/lib/src/sentry_trace_context_header.dart - - - - - -/lib/src/sentry_client.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_client.dart->/lib/src/sentry_attachment/sentry_attachment.dart - - - - - -/lib/src/sentry_client.dart->/lib/src/sentry_user_feedback.dart - - - - - -/lib/src/transport/noop_transport.dart - -noop_transport - - - -/lib/src/sentry_client.dart->/lib/src/transport/noop_transport.dart - - - - - -/lib/src/transport/data_category.dart - -data_category - - - -/lib/src/sentry_client.dart->/lib/src/transport/data_category.dart - - - - - -/lib/src/transport/rate_limiter.dart - -rate_limiter - - - -/lib/src/sentry_client.dart->/lib/src/transport/rate_limiter.dart - - - - - -/lib/src/transport/http_transport.dart - -http_transport - - - -/lib/src/sentry_client.dart->/lib/src/transport/http_transport.dart - - - - - -/lib/src/sentry_client.dart->/lib/src/scope.dart - - - - - -/lib/src/event_processor.dart - -event_processor - - - -/lib/src/sentry_client.dart->/lib/src/event_processor.dart - - - - - -/lib/src/utils/isolate_utils.dart - -isolate_utils - - - -/lib/src/sentry_client.dart->/lib/src/utils/isolate_utils.dart - - - - - -/lib/src/client_reports/client_report_recorder.dart - -client_report_recorder - - - -/lib/src/sentry_client.dart->/lib/src/client_reports/client_report_recorder.dart - - - - - -/lib/src/client_reports/discard_reason.dart - -discard_reason - - - -/lib/src/sentry_client.dart->/lib/src/client_reports/discard_reason.dart - - - - - -/lib/src/sentry_stack_trace_factory.dart - -sentry_stack_trace_factory - - - -/lib/src/sentry_client.dart->/lib/src/sentry_stack_trace_factory.dart - - - - - -/lib/src/sentry_envelope.dart - -sentry_envelope - - - -/lib/src/sentry_client.dart->/lib/src/sentry_envelope.dart - - - - - -/lib/src/sentry_exception_factory.dart - -sentry_exception_factory - - - -/lib/src/sentry_client.dart->/lib/src/sentry_exception_factory.dart - - - - - -/lib/src/protocol/span_id.dart - -span_id - - - -/lib/src/protocol/sentry_response.dart - -sentry_response - - - -/lib/src/protocol/contexts.dart - -contexts - - - -/lib/src/protocol/sentry_response.dart->/lib/src/protocol/contexts.dart - - - - - -/lib/src/utils/iterable_extension.dart - -iterable_extension - - - -/lib/src/protocol/sentry_response.dart->/lib/src/utils/iterable_extension.dart - - - - - -/lib/src/protocol/sentry_thread.dart - -sentry_thread - - - -/lib/src/protocol/sentry_stack_trace.dart - -sentry_stack_trace - - - -/lib/src/protocol/sentry_thread.dart->/lib/src/protocol/sentry_stack_trace.dart - - - - - -/lib/src/protocol/sentry_stack_frame.dart - -sentry_stack_frame - - - -/lib/src/protocol/sentry_transaction_info.dart - -sentry_transaction_info - - - -/lib/src/protocol/sentry_span.dart - -sentry_span - - - -/lib/src/protocol/sentry_span.dart->/lib/src/utils.dart - - - - - -/lib/src/protocol/sentry_span.dart->/lib/src/tracing.dart - - - - - -/lib/src/protocol/sentry_span.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_tracer.dart - -sentry_tracer - - - -/lib/src/protocol/sentry_span.dart->/lib/src/sentry_tracer.dart - - - - - -/lib/src/protocol/sentry_span.dart->/lib/src/hub.dart - - - - - -/lib/src/protocol/breadcrumb.dart - -breadcrumb - - - -/lib/src/protocol/breadcrumb.dart->/lib/src/utils.dart - - - - - -/lib/src/protocol/breadcrumb.dart->/lib/src/protocol.dart - - - - - -/lib/src/protocol/sentry_request.dart - -sentry_request - - - -/lib/src/utils/http_sanitizer.dart - -http_sanitizer - - - -/lib/src/protocol/sentry_request.dart->/lib/src/utils/http_sanitizer.dart - - - - - -/lib/src/protocol/sentry_request.dart->/lib/src/utils/iterable_extension.dart - - - - - -/lib/src/protocol/sentry_level.dart - -sentry_level - - - -/lib/src/protocol/sdk_version.dart - -sdk_version - - - -/lib/src/protocol/sentry_package.dart - -sentry_package - - - -/lib/src/protocol/sdk_version.dart->/lib/src/protocol/sentry_package.dart - - - - - -/lib/src/protocol/sentry_trace_header.dart - -sentry_trace_header - - - -/lib/src/protocol/sentry_trace_header.dart->/lib/src/invalid_sentry_trace_header_exception.dart - - - - - -/lib/src/protocol/sentry_trace_header.dart->/lib/src/protocol.dart - - - - - -/lib/src/protocol/sentry_transaction.dart - -sentry_transaction - - - -/lib/src/protocol/sentry_transaction.dart->/lib/src/utils.dart - - - - - -/lib/src/protocol/sentry_transaction.dart->/lib/src/protocol.dart - - - - - -/lib/src/protocol/sentry_transaction.dart->/lib/src/sentry_tracer.dart - - - - - -/lib/src/sentry_measurement.dart - -sentry_measurement - - - -/lib/src/protocol/sentry_transaction.dart->/lib/src/sentry_measurement.dart - - - - - -/lib/src/protocol/sentry_id.dart - -sentry_id - - - -/lib/src/protocol/sentry_view_hierarchy_element.dart - -sentry_view_hierarchy_element - - - -/lib/src/protocol/sentry_culture.dart - -sentry_culture - - - -/lib/src/protocol/sentry_geo.dart - -sentry_geo - - - -/lib/src/protocol/debug_meta.dart - -debug_meta - - - -/lib/src/protocol/debug_meta.dart->/lib/src/protocol.dart - - - - - -/lib/src/protocol/mechanism.dart - -mechanism - - - -/lib/src/protocol/sentry_app.dart - -sentry_app - - - -/lib/src/protocol/sentry_gpu.dart - -sentry_gpu - - - -/lib/src/protocol/sentry_trace_context.dart - -sentry_trace_context - - - -/lib/src/protocol/sentry_trace_context.dart->/lib/src/propagation_context.dart - - - - - -/lib/src/protocol/sentry_trace_context.dart->/lib/src/protocol.dart - - - - - -/lib/src/protocol/sentry_trace_context.dart->/lib/sentry.dart - - - - - -/lib/src/protocol/contexts.dart->/lib/src/protocol.dart - - - - - -/lib/src/protocol/debug_image.dart - -debug_image - - - -/lib/src/protocol/sentry_baggage_header.dart - -sentry_baggage_header - - - -/lib/src/protocol/sentry_baggage_header.dart->/lib/src/sentry_baggage.dart - - - - - -/lib/src/protocol/sentry_stack_trace.dart->/lib/src/protocol/sentry_stack_frame.dart - - - - - -/lib/src/protocol/sentry_runtime.dart - -sentry_runtime - - - -/lib/src/protocol/sentry_device.dart - -sentry_device - - - -/lib/src/protocol/sentry_device.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/protocol/sentry_message.dart - -sentry_message - - - -/lib/src/protocol/sdk_info.dart - -sdk_info - - - -/lib/src/protocol/max_body_size.dart - -max_body_size - - - -/lib/src/protocol/sentry_event.dart - -sentry_event - - - -/lib/src/protocol/sentry_event.dart->/lib/src/utils.dart - - - - - -/lib/src/throwable_mechanism.dart - -throwable_mechanism - - - -/lib/src/protocol/sentry_event.dart->/lib/src/throwable_mechanism.dart - - - - - -/lib/src/protocol/sentry_event.dart->/lib/src/protocol.dart - - - - - -/lib/src/protocol/sentry_operating_system.dart - -sentry_operating_system - - - -/lib/src/protocol/sentry_user.dart - -sentry_user - - - -/lib/src/protocol/sentry_user.dart->/lib/sentry.dart - - - - - -/lib/src/protocol/sentry_exception.dart - -sentry_exception - - - -/lib/src/protocol/sentry_exception.dart->/lib/src/protocol.dart - - - - - -/lib/src/protocol/span_status.dart - -span_status - - - -/lib/src/protocol/sentry_transaction_name_source.dart - -sentry_transaction_name_source - - - -/lib/src/protocol/sentry_view_hierarchy.dart - -sentry_view_hierarchy - - - -/lib/src/protocol/sentry_view_hierarchy.dart->/lib/src/protocol/sentry_view_hierarchy_element.dart - - - - - -/lib/src/protocol/sentry_browser.dart - -sentry_browser - - - -/lib/src/protocol/dsn.dart - -dsn - - - -/lib/src/sentry_baggage.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/sentry_baggage.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_baggage.dart->/lib/src/scope.dart - - - - - -/lib/src/exception_stacktrace_extractor.dart - -exception_stacktrace_extractor - - - -/lib/src/exception_stacktrace_extractor.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/exception_stacktrace_extractor.dart->/lib/src/protocol.dart - - - - - -/lib/src/isolate_error_integration.dart - -isolate_error_integration - - - -/lib/src/isolate_error_integration.dart->/lib/src/sentry_isolate_extension.dart - - - - - -/lib/src/isolate_error_integration.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/isolate_error_integration.dart->/lib/src/integration.dart - - - - - -/lib/src/isolate_error_integration.dart->/lib/src/hub.dart - - - - - -/lib/src/throwable_mechanism.dart->/lib/src/protocol/mechanism.dart - - - - - -/lib/src/type_check_hint.dart - -type_check_hint - - - -/lib/src/http_client/failed_request_client.dart - -failed_request_client - - - -/lib/src/type_check_hint.dart->/lib/src/http_client/failed_request_client.dart - - - - - -/lib/src/sentry_client_stub.dart - -sentry_client_stub - - - -/lib/src/sentry_client_stub.dart->/lib/src/sentry_client.dart - - - - - -/lib/src/sentry_client_stub.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/sentry_options.dart->/lib/src/version.dart - - - - - -/lib/src/sentry_options.dart->/lib/src/environment/environment_variables.dart - - - - - -/lib/src/sentry_options.dart->/lib/src/diagnostic_logger.dart - - - - - -/lib/src/noop_client.dart - -noop_client - - - -/lib/src/sentry_options.dart->/lib/src/noop_client.dart - - - - - -/lib/src/sentry_options.dart->/lib/src/transport/noop_transport.dart - - - - - -/lib/src/client_reports/noop_client_report_recorder.dart - -noop_client_report_recorder - - - -/lib/src/sentry_options.dart->/lib/src/client_reports/noop_client_report_recorder.dart - - - - - -/lib/src/sentry_options.dart->/lib/src/client_reports/client_report_recorder.dart - - - - - -/lib/src/sentry_options.dart->/lib/src/sentry_stack_trace_factory.dart - - - - - -/lib/src/sentry_options.dart->/lib/src/sentry_exception_factory.dart - - - - - -/lib/src/sentry_options.dart->/lib/sentry.dart - - - - - -/lib/src/event_processor/deduplication_event_processor.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/event_processor/deduplication_event_processor.dart->/lib/src/hint.dart - - - - - -/lib/src/event_processor/deduplication_event_processor.dart->/lib/src/protocol.dart - - - - - -/lib/src/event_processor/deduplication_event_processor.dart->/lib/src/event_processor.dart - - - - - -/lib/src/event_processor/exception/exception_event_processor.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/event_processor/exception/exception_event_processor.dart->/lib/src/event_processor.dart - - - - - -/lib/src/event_processor/exception/web_exception_event_processor.dart - -web_exception_event_processor - - - -/lib/src/event_processor/exception/web_exception_event_processor.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/event_processor/exception/web_exception_event_processor.dart->/lib/src/event_processor/exception/exception_event_processor.dart - - - - - -/lib/src/event_processor/exception/web_exception_event_processor.dart->/lib/src/hint.dart - - - - - -/lib/src/event_processor/exception/web_exception_event_processor.dart->/lib/src/protocol.dart - - - - - -/lib/src/event_processor/exception/io_exception_event_processor.dart - -io_exception_event_processor - - - -/lib/src/event_processor/exception/io_exception_event_processor.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/event_processor/exception/io_exception_event_processor.dart->/lib/src/event_processor/exception/exception_event_processor.dart - - - - - -/lib/src/event_processor/exception/io_exception_event_processor.dart->/lib/src/hint.dart - - - - - -/lib/src/event_processor/exception/io_exception_event_processor.dart->/lib/src/protocol.dart - - - - - -/lib/src/event_processor/enricher/io_enricher_event_processor.dart - -io_enricher_event_processor - - - -/lib/src/event_processor/enricher/io_enricher_event_processor.dart->/lib/src/event_processor/enricher/enricher_event_processor.dart - - - - - -/lib/src/event_processor/enricher/io_enricher_event_processor.dart->/lib/sentry.dart - - - - - -/lib/src/event_processor/enricher/enricher_event_processor.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/event_processor/enricher/enricher_event_processor.dart->/lib/src/event_processor.dart - - - - - -/lib/src/event_processor/enricher/web_enricher_event_processor.dart - -web_enricher_event_processor - - - -/lib/src/event_processor/enricher/web_enricher_event_processor.dart->/lib/src/event_processor/enricher/enricher_event_processor.dart - - - - - -/lib/src/event_processor/enricher/web_enricher_event_processor.dart->/lib/sentry.dart - - - - - -/lib/src/platform/platform.dart - -platform - - - -/lib/src/platform/_web_platform.dart - -_web_platform - - - -/lib/src/platform/_web_platform.dart->/lib/src/platform/platform.dart - - - - - -/lib/src/platform/_io_platform.dart - -_io_platform - - - -/lib/src/platform/_io_platform.dart->/lib/src/platform/platform.dart - - - - - -/lib/src/hint.dart->/lib/src/sentry_attachment/sentry_attachment.dart - - - - - -/lib/src/platform_checker.dart->/lib/src/platform/platform.dart - - - - - -/lib/src/tracing.dart->/lib/src/sentry_span_context.dart - - - - - -/lib/src/tracing.dart->/lib/src/invalid_sentry_trace_header_exception.dart - - - - - -/lib/src/tracing.dart->/lib/src/sentry_trace_context_header.dart - - - - - -/lib/src/sentry_measurement_unit.dart - -sentry_measurement_unit - - - -/lib/src/tracing.dart->/lib/src/sentry_measurement_unit.dart - - - - - -/lib/src/sentry_transaction_context.dart - -sentry_transaction_context - - - -/lib/src/tracing.dart->/lib/src/sentry_transaction_context.dart - - - - - -/lib/src/sentry_span_interface.dart - -sentry_span_interface - - - -/lib/src/tracing.dart->/lib/src/sentry_span_interface.dart - - - - - -/lib/src/sentry_traces_sampling_decision.dart - -sentry_traces_sampling_decision - - - -/lib/src/tracing.dart->/lib/src/sentry_traces_sampling_decision.dart - - - - - -/lib/src/noop_sentry_span.dart - -noop_sentry_span - - - -/lib/src/tracing.dart->/lib/src/noop_sentry_span.dart - - - - - -/lib/src/tracing.dart->/lib/src/sentry_measurement.dart - - - - - -/lib/src/sentry_sampling_context.dart - -sentry_sampling_context - - - -/lib/src/tracing.dart->/lib/src/sentry_sampling_context.dart - - - - - -/lib/src/exception_cause.dart - -exception_cause - - - -/lib/src/noop_isolate_error_integration.dart - -noop_isolate_error_integration - - - -/lib/src/noop_isolate_error_integration.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/noop_isolate_error_integration.dart->/lib/src/integration.dart - - - - - -/lib/src/noop_isolate_error_integration.dart->/lib/src/hub.dart - - - - - -/lib/src/sentry_trace_context_header.dart->/lib/src/protocol/sentry_id.dart - - - - - -/lib/src/sentry_trace_context_header.dart->/lib/src/sentry_baggage.dart - - - - - -/lib/src/sentry_trace_context_header.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/integration.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/integration.dart->/lib/src/hub.dart - - - - - -/lib/src/protocol.dart->/lib/src/sentry_event_like.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/span_id.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_response.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_thread.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_stack_frame.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_transaction_info.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_span.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/breadcrumb.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_request.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_level.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sdk_version.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_trace_header.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_transaction.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_id.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_view_hierarchy_element.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_culture.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_geo.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/debug_meta.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/mechanism.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_app.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_gpu.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_trace_context.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/contexts.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/debug_image.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_baggage_header.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_stack_trace.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_runtime.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_device.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_message.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sdk_info.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/max_body_size.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_package.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_event.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_operating_system.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_user.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_exception.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/span_status.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_transaction_name_source.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_view_hierarchy.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/sentry_browser.dart - - - - - -/lib/src/protocol.dart->/lib/src/protocol/dsn.dart - - - - - -/lib/src/sentry_attachment/io_sentry_attachment.dart - -io_sentry_attachment - - - -/lib/src/sentry_attachment/io_sentry_attachment.dart->/lib/src/sentry_attachment/sentry_attachment.dart - - - - - -/lib/src/sentry_attachment/sentry_attachment.dart->/lib/src/utils.dart - - - - - -/lib/src/sentry_attachment/sentry_attachment.dart->/lib/src/protocol/sentry_view_hierarchy.dart - - - - - -/lib/src/exception_cause_extractor.dart - -exception_cause_extractor - - - -/lib/src/exception_cause_extractor.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/exception_cause_extractor.dart->/lib/src/exception_cause.dart - - - - - -/lib/src/exception_cause_extractor.dart->/lib/src/protocol.dart - - - - - -/lib/src/noop_sentry_client.dart - -noop_sentry_client - - - -/lib/src/noop_sentry_client.dart->/lib/src/sentry_client.dart - - - - - -/lib/src/noop_sentry_client.dart->/lib/src/hint.dart - - - - - -/lib/src/noop_sentry_client.dart->/lib/src/sentry_trace_context_header.dart - - - - - -/lib/src/noop_sentry_client.dart->/lib/src/protocol.dart - - - - - -/lib/src/noop_sentry_client.dart->/lib/src/sentry_user_feedback.dart - - - - - -/lib/src/noop_sentry_client.dart->/lib/src/scope.dart - - - - - -/lib/src/noop_sentry_client.dart->/lib/src/sentry_envelope.dart - - - - - -/lib/src/sentry_transaction_context.dart->/lib/src/sentry_trace_origins.dart - - - - - -/lib/src/sentry_transaction_context.dart->/lib/src/sentry_baggage.dart - - - - - -/lib/src/sentry_transaction_context.dart->/lib/src/tracing.dart - - - - - -/lib/src/sentry_transaction_context.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_tracer_finish_status.dart - -sentry_tracer_finish_status - - - -/lib/src/sentry_tracer.dart->/lib/src/sentry_tracer_finish_status.dart - - - - - -/lib/src/utils/sample_rate_format.dart - -sample_rate_format - - - -/lib/src/sentry_tracer.dart->/lib/src/utils/sample_rate_format.dart - - - - - -/lib/src/sentry_tracer.dart->/lib/sentry.dart - - - - - -/lib/src/sentry_span_interface.dart->/lib/src/tracing.dart - - - - - -/lib/src/sentry_span_interface.dart->/lib/src/protocol.dart - - - - - -/lib/src/noop_origin.dart - -noop_origin - - - -/lib/src/hub_adapter.dart->/lib/src/sentry.dart - - - - - -/lib/src/hub_adapter.dart->/lib/src/sentry_client.dart - - - - - -/lib/src/hub_adapter.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/hub_adapter.dart->/lib/src/hint.dart - - - - - -/lib/src/hub_adapter.dart->/lib/src/tracing.dart - - - - - -/lib/src/hub_adapter.dart->/lib/src/protocol.dart - - - - - -/lib/src/hub_adapter.dart->/lib/src/sentry_user_feedback.dart - - - - - -/lib/src/hub_adapter.dart->/lib/src/scope.dart - - - - - -/lib/src/hub_adapter.dart->/lib/src/hub.dart - - - - - -/lib/src/sentry_user_feedback.dart->/lib/src/protocol.dart - - - - - -/lib/src/http_client/failed_request_client.dart->/lib/src/throwable_mechanism.dart - - - - - -/lib/src/http_client/failed_request_client.dart->/lib/src/type_check_hint.dart - - - - - -/lib/src/http_client/failed_request_client.dart->/lib/src/hint.dart - - - - - -/lib/src/http_client/failed_request_client.dart->/lib/src/protocol.dart - - - - - -/lib/src/http_client/failed_request_client.dart->/lib/src/hub_adapter.dart - - - - - -/lib/src/http_client/sentry_http_client.dart - -sentry_http_client - - - -/lib/src/http_client/failed_request_client.dart->/lib/src/http_client/sentry_http_client.dart - - - - - -/lib/src/http_client/sentry_http_client_error.dart - -sentry_http_client_error - - - -/lib/src/http_client/failed_request_client.dart->/lib/src/http_client/sentry_http_client_error.dart - - - - - -/lib/src/utils/tracing_utils.dart - -tracing_utils - - - -/lib/src/http_client/failed_request_client.dart->/lib/src/utils/tracing_utils.dart - - - - - -/lib/src/http_client/failed_request_client.dart->/lib/src/hub.dart - - - - - -/lib/src/http_client/sentry_http_client.dart->/lib/src/hub_adapter.dart - - - - - -/lib/src/http_client/sentry_http_client.dart->/lib/src/http_client/failed_request_client.dart - - - - - -/lib/src/http_client/breadcrumb_client.dart - -breadcrumb_client - - - -/lib/src/http_client/sentry_http_client.dart->/lib/src/http_client/breadcrumb_client.dart - - - - - -/lib/src/http_client/tracing_client.dart - -tracing_client - - - -/lib/src/http_client/sentry_http_client.dart->/lib/src/http_client/tracing_client.dart - - - - - -/lib/src/http_client/sentry_http_client.dart->/lib/src/hub.dart - - - - - -/lib/src/http_client/breadcrumb_client.dart->/lib/src/protocol.dart - - - - - -/lib/src/http_client/breadcrumb_client.dart->/lib/src/hub_adapter.dart - - - - - -/lib/src/http_client/breadcrumb_client.dart->/lib/src/utils/http_sanitizer.dart - - - - - -/lib/src/utils/url_details.dart - -url_details - - - -/lib/src/http_client/breadcrumb_client.dart->/lib/src/utils/url_details.dart - - - - - -/lib/src/http_client/breadcrumb_client.dart->/lib/src/hub.dart - - - - - -/lib/src/http_client/tracing_client.dart->/lib/src/sentry_trace_origins.dart - - - - - -/lib/src/http_client/tracing_client.dart->/lib/src/tracing.dart - - - - - -/lib/src/http_client/tracing_client.dart->/lib/src/protocol.dart - - - - - -/lib/src/http_client/tracing_client.dart->/lib/src/hub_adapter.dart - - - - - -/lib/src/http_client/tracing_client.dart->/lib/src/utils/http_sanitizer.dart - - - - - -/lib/src/http_client/tracing_client.dart->/lib/src/utils/tracing_utils.dart - - - - - -/lib/src/http_client/tracing_client.dart->/lib/src/hub.dart - - - - - -/lib/src/noop_sentry_span.dart->/lib/src/utils.dart - - - - - -/lib/src/noop_sentry_span.dart->/lib/src/tracing.dart - - - - - -/lib/src/noop_sentry_span.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_tracer_finish_status.dart->/lib/sentry.dart - - - - - -/lib/src/run_zoned_guarded_integration.dart->/lib/src/throwable_mechanism.dart - - - - - -/lib/src/run_zoned_guarded_integration.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/run_zoned_guarded_integration.dart->/lib/src/integration.dart - - - - - -/lib/src/run_zoned_guarded_integration.dart->/lib/src/protocol.dart - - - - - -/lib/src/run_zoned_guarded_integration.dart->/lib/src/hub.dart - - - - - -/lib/src/transport/noop_transport.dart->/lib/src/protocol.dart - - - - - -/lib/src/transport/transport.dart - -transport - - - -/lib/src/transport/noop_transport.dart->/lib/src/transport/transport.dart - - - - - -/lib/src/transport/noop_transport.dart->/lib/src/sentry_envelope.dart - - - - - -/lib/src/transport/rate_limit_parser.dart - -rate_limit_parser - - - -/lib/src/transport/rate_limit_parser.dart->/lib/src/transport/data_category.dart - - - - - -/lib/src/transport/rate_limit.dart - -rate_limit - - - -/lib/src/transport/rate_limit_parser.dart->/lib/src/transport/rate_limit.dart - - - - - -/lib/src/transport/noop_encode.dart - -noop_encode - - - -/lib/src/transport/transport.dart->/lib/src/protocol.dart - - - - - -/lib/src/transport/transport.dart->/lib/src/sentry_envelope.dart - - - - - -/lib/src/transport/rate_limiter.dart->/lib/src/sentry_envelope_item.dart - - - - - -/lib/src/transport/rate_limiter.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/transport/rate_limiter.dart->/lib/src/transport/rate_limit_parser.dart - - - - - -/lib/src/transport/rate_limiter.dart->/lib/src/transport/data_category.dart - - - - - -/lib/src/transport/rate_limiter.dart->/lib/src/transport/rate_limit.dart - - - - - -/lib/src/transport/rate_limiter.dart->/lib/src/client_reports/discard_reason.dart - - - - - -/lib/src/transport/rate_limiter.dart->/lib/src/sentry_envelope.dart - - - - - -/lib/src/transport/encode.dart - -encode - - - -/lib/src/transport/rate_limit.dart->/lib/src/transport/data_category.dart - - - - - -/lib/src/transport/http_transport.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/transport/http_transport.dart->/lib/src/noop_client.dart - - - - - -/lib/src/transport/http_transport.dart->/lib/src/protocol.dart - - - - - -/lib/src/transport/http_transport.dart->/lib/src/transport/noop_encode.dart - - - - - -/lib/src/transport/http_transport.dart->/lib/src/transport/data_category.dart - - - - - -/lib/src/transport/http_transport.dart->/lib/src/transport/transport.dart - - - - - -/lib/src/transport/http_transport.dart->/lib/src/transport/rate_limiter.dart - - - - - -/lib/src/transport/http_transport.dart->/lib/src/client_reports/client_report_recorder.dart - - - - - -/lib/src/transport/http_transport.dart->/lib/src/client_reports/discard_reason.dart - - - - - -/lib/src/transport/http_transport.dart->/lib/src/sentry_envelope.dart - - - - - -/lib/src/recursive_exception_cause_extractor.dart - -recursive_exception_cause_extractor - - - -/lib/src/recursive_exception_cause_extractor.dart->/lib/src/throwable_mechanism.dart - - - - - -/lib/src/recursive_exception_cause_extractor.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/recursive_exception_cause_extractor.dart->/lib/src/exception_cause.dart - - - - - -/lib/src/recursive_exception_cause_extractor.dart->/lib/src/protocol.dart - - - - - -/lib/src/scope.dart->/lib/src/propagation_context.dart - - - - - -/lib/src/scope.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/scope.dart->/lib/src/hint.dart - - - - - -/lib/src/scope.dart->/lib/src/protocol.dart - - - - - -/lib/src/scope.dart->/lib/src/sentry_attachment/sentry_attachment.dart - - - - - -/lib/src/scope.dart->/lib/src/sentry_tracer.dart - - - - - -/lib/src/scope.dart->/lib/src/sentry_span_interface.dart - - - - - -/lib/src/scope.dart->/lib/src/event_processor.dart - - - - - -/lib/src/scope_observer.dart - -scope_observer - - - -/lib/src/scope.dart->/lib/src/scope_observer.dart - - - - - -/lib/src/event_processor.dart->/lib/src/hint.dart - - - - - -/lib/src/event_processor.dart->/lib/src/protocol.dart - - - - - -/lib/src/utils/http_sanitizer.dart->/lib/src/protocol.dart - - - - - -/lib/src/utils/http_sanitizer.dart->/lib/src/utils/url_details.dart - - - - - -/lib/src/utils/url_details.dart->/lib/sentry.dart - - - - - -/lib/src/utils/http_header_utils.dart - -http_header_utils - - - -/lib/src/utils/_web_get_isolate_name.dart - -_web_get_isolate_name - - - -/lib/src/utils/tracing_utils.dart->/lib/sentry.dart - - - - - -/lib/src/utils/_io_get_isolate_name.dart - -_io_get_isolate_name - - - -/lib/src/client_reports/client_report.dart->/lib/src/utils.dart - - - - - -/lib/src/client_reports/discarded_event.dart - -discarded_event - - - -/lib/src/client_reports/client_report.dart->/lib/src/client_reports/discarded_event.dart - - - - - -/lib/src/client_reports/noop_client_report_recorder.dart->/lib/src/transport/data_category.dart - - - - - -/lib/src/client_reports/noop_client_report_recorder.dart->/lib/src/client_reports/client_report.dart - - - - - -/lib/src/client_reports/noop_client_report_recorder.dart->/lib/src/client_reports/client_report_recorder.dart - - - - - -/lib/src/client_reports/noop_client_report_recorder.dart->/lib/src/client_reports/discard_reason.dart - - - - - -/lib/src/client_reports/discarded_event.dart->/lib/src/transport/data_category.dart - - - - - -/lib/src/client_reports/discarded_event.dart->/lib/src/client_reports/discard_reason.dart - - - - - -/lib/src/client_reports/client_report_recorder.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/client_reports/client_report_recorder.dart->/lib/src/transport/data_category.dart - - - - - -/lib/src/client_reports/client_report_recorder.dart->/lib/src/client_reports/client_report.dart - - - - - -/lib/src/client_reports/client_report_recorder.dart->/lib/src/client_reports/discarded_event.dart - - - - - -/lib/src/client_reports/client_report_recorder.dart->/lib/src/client_reports/discard_reason.dart - - - - - -/lib/src/sentry_stack_trace_factory.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/sentry_stack_trace_factory.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_stack_trace_factory.dart->/lib/src/noop_origin.dart - - - - - -/lib/src/sentry_measurement.dart->/lib/src/sentry_measurement_unit.dart - - - - - -/lib/src/sentry_envelope_header.dart - -sentry_envelope_header - - - -/lib/src/sentry_envelope_header.dart->/lib/src/utils.dart - - - - - -/lib/src/sentry_envelope_header.dart->/lib/src/protocol/sdk_version.dart - - - - - -/lib/src/sentry_envelope_header.dart->/lib/src/protocol/sentry_id.dart - - - - - -/lib/src/sentry_envelope_header.dart->/lib/src/sentry_trace_context_header.dart - - - - - -/lib/src/sentry_isolate.dart->/lib/src/throwable_mechanism.dart - - - - - -/lib/src/sentry_isolate.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_isolate.dart->/lib/src/hub_adapter.dart - - - - - -/lib/src/sentry_isolate.dart->/lib/src/hub.dart - - - - - -/lib/src/scope_observer.dart->/lib/src/protocol/breadcrumb.dart - - - - - -/lib/src/scope_observer.dart->/lib/src/protocol/sentry_user.dart - - - - - -/lib/src/sentry_envelope.dart->/lib/src/utils.dart - - - - - -/lib/src/sentry_envelope.dart->/lib/src/sentry_item_type.dart - - - - - -/lib/src/sentry_envelope.dart->/lib/src/sentry_envelope_item.dart - - - - - -/lib/src/sentry_envelope.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/sentry_envelope.dart->/lib/src/sentry_trace_context_header.dart - - - - - -/lib/src/sentry_envelope.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_envelope.dart->/lib/src/sentry_attachment/sentry_attachment.dart - - - - - -/lib/src/sentry_envelope.dart->/lib/src/sentry_user_feedback.dart - - - - - -/lib/src/sentry_envelope.dart->/lib/src/client_reports/client_report.dart - - - - - -/lib/src/sentry_envelope.dart->/lib/src/sentry_envelope_header.dart - - - - - -/lib/src/sentry_sampling_context.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/sentry_sampling_context.dart->/lib/src/tracing.dart - - - - - -/lib/src/sentry_exception_factory.dart->/lib/src/throwable_mechanism.dart - - - - - -/lib/src/sentry_exception_factory.dart->/lib/src/sentry_options.dart - - - - - -/lib/src/sentry_exception_factory.dart->/lib/src/protocol.dart - - - - - -/lib/src/sentry_exception_factory.dart->/lib/src/recursive_exception_cause_extractor.dart - - - - - -/lib/src/sentry_exception_factory.dart->/lib/src/sentry_stack_trace_factory.dart - - - - - -/lib/src/hub.dart->/lib/src/propagation_context.dart - - - - - -/lib/src/hub.dart->/lib/src/sentry_traces_sampler.dart - - - - - -/lib/src/hub.dart->/lib/src/sentry_tracer.dart - - - - - -/lib/src/hub.dart->/lib/src/transport/data_category.dart - - - - - -/lib/src/hub.dart->/lib/src/client_reports/discard_reason.dart - - - - - -/lib/src/hub.dart->/lib/sentry.dart - - - - - -/lib/src/origin.dart - -origin - - - -/lib/sentry.dart->/lib/src/utils.dart - - - - - -/lib/sentry.dart->/lib/src/sentry_trace_origins.dart - - - - - -/lib/sentry.dart->/lib/src/sentry.dart - - - - - -/lib/sentry.dart->/lib/src/sentry_envelope_item.dart - - - - - -/lib/sentry.dart->/lib/src/sentry_client.dart - - - - - -/lib/sentry.dart->/lib/src/sentry_baggage.dart - - - - - -/lib/sentry.dart->/lib/src/exception_stacktrace_extractor.dart - - - - - -/lib/sentry.dart->/lib/src/throwable_mechanism.dart - - - - - -/lib/sentry.dart->/lib/src/type_check_hint.dart - - - - - -/lib/sentry.dart->/lib/src/sentry_options.dart - - - - - -/lib/sentry.dart->/lib/src/hint.dart - - - - - -/lib/sentry.dart->/lib/src/platform_checker.dart - - - - - -/lib/sentry.dart->/lib/src/tracing.dart - - - - - -/lib/sentry.dart->/lib/src/exception_cause.dart - - - - - -/lib/sentry.dart->/lib/src/integration.dart - - - - - -/lib/sentry.dart->/lib/src/protocol.dart - - - - - -/lib/sentry.dart->/lib/src/sentry_attachment/sentry_attachment.dart - - - - - -/lib/sentry.dart->/lib/src/exception_cause_extractor.dart - - - - - -/lib/sentry.dart->/lib/src/hub_adapter.dart - - - - - -/lib/sentry.dart->/lib/src/sentry_user_feedback.dart - - - - - -/lib/sentry.dart->/lib/src/http_client/sentry_http_client.dart - - - - - -/lib/sentry.dart->/lib/src/http_client/sentry_http_client_error.dart - - - - - -/lib/sentry.dart->/lib/src/run_zoned_guarded_integration.dart - - - - - -/lib/sentry.dart->/lib/src/transport/transport.dart - - - - - -/lib/sentry.dart->/lib/src/scope.dart - - - - - -/lib/sentry.dart->/lib/src/event_processor.dart - - - - - -/lib/sentry.dart->/lib/src/utils/http_sanitizer.dart - - - - - -/lib/sentry.dart->/lib/src/utils/url_details.dart - - - - - -/lib/sentry.dart->/lib/src/utils/http_header_utils.dart - - - - - -/lib/sentry.dart->/lib/src/utils/tracing_utils.dart - - - - - -/lib/sentry.dart->/lib/src/scope_observer.dart - - - - - -/lib/sentry.dart->/lib/src/sentry_envelope.dart - - - - - -/lib/sentry.dart->/lib/src/hub.dart - - - - - -/lib/sentry_io.dart - -sentry_io - - - -/lib/sentry_io.dart->/lib/src/sentry_isolate_extension.dart - - - - - -/lib/sentry_io.dart->/lib/src/sentry_attachment/io_sentry_attachment.dart - - - - - -/lib/sentry_io.dart->/lib/src/sentry_isolate.dart - - - - - -/lib/sentry_io.dart->/lib/sentry.dart - - - - - diff --git a/dart/dartdoc_options.yaml b/dart/dartdoc_options.yaml deleted file mode 100644 index 20edf7f942..0000000000 --- a/dart/dartdoc_options.yaml +++ /dev/null @@ -1,3 +0,0 @@ -dartdoc: - errors: - - unresolved-doc-reference diff --git a/dart/example/.gitignore b/dart/example/.gitignore deleted file mode 100644 index 3c8a157278..0000000000 --- a/dart/example/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# Files and directories created by pub. -.dart_tool/ -.packages - -# Conventional directory for build output. -build/ diff --git a/dart/example/README.md b/dart/example/README.md deleted file mode 100644 index 9e62e425d0..0000000000 --- a/dart/example/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# `package:sentry` example - -The example in this directory throws an error and sends it to Sentry.io. Use it -as a source of example code, or to smoke-test your Sentry.io configuration. - -To use the example, create a Sentry.io account and get a DSN for your project. -In the `main.dart` file, replace the `dsn` value with the one you got from -Sentry.io. Then run the following command : - - -```sh -dart pub get -dart run -``` diff --git a/dart/example/analysis_options.yaml b/dart/example/analysis_options.yaml deleted file mode 100644 index f6968116a5..0000000000 --- a/dart/example/analysis_options.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Defines a default set of lint rules enforced for projects at Google. For -# details and rationale, see -# https://github.com/dart-lang/pedantic#enabled-lints. - -include: package:lints/recommended.yaml \ No newline at end of file diff --git a/dart/example/bin/event_example.dart b/dart/example/bin/event_example.dart deleted file mode 100644 index 40d7932474..0000000000 --- a/dart/example/bin/event_example.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:sentry/src/protocol.dart'; - -final event = SentryEvent( - logger: 'main', - serverName: 'server.dart', - release: '1.4.0-preview.1', - environment: 'Test', - message: SentryMessage('This is an example Dart event.'), - tags: const {'project-id': '7371'}, - // ignore: deprecated_member_use - extra: const {'section': '1'}, - // fingerprint: const ['example-dart'], fingerprint forces events to group together - user: SentryUser( - id: '800', - username: 'first-user', - email: 'first@user.lan', - // ipAddress: '127.0.0.1', sendDefaultPii feature is enabled - // ignore: deprecated_member_use - extras: {'first-sign-in': '2020-01-01'}, - ), - breadcrumbs: [ - Breadcrumb( - message: 'UI Lifecycle', - timestamp: DateTime.now().toUtc(), - category: 'ui.lifecycle', - type: 'navigation', - data: {'screen': 'MainActivity', 'state': 'created'}, - level: SentryLevel.info, - ) - ], - contexts: Contexts( - operatingSystem: SentryOperatingSystem( - name: 'Android', - version: '5.0.2', - build: 'LRX22G.P900XXS0BPL2', - kernelVersion: - 'Linux version 3.4.39-5726670 (dpi@SWHC3807) (gcc version 4.8 (GCC) ) #1 SMP PREEMPT Thu Dec 1 19:42:39 KST 2016', - rooted: false, - ), - runtimes: [SentryRuntime(name: 'ART', version: '5')], - app: SentryApp( - name: 'Example Dart App', - version: '1.42.0', - identifier: 'HGT-App-13', - build: '93785', - buildType: 'release', - deviceAppHash: '5afd3a6', - startTime: DateTime.now().toUtc(), - ), - browser: SentryBrowser(name: 'Firefox', version: '42.0.1'), - device: SentryDevice( - name: 'SM-P900', - family: 'SM-P900', - model: 'SM-P900 (LRX22G)', - modelId: 'LRX22G', - arch: 'armeabi-v7a', - batteryLevel: 99, - orientation: SentryOrientation.landscape, - manufacturer: 'samsung', - brand: 'samsung', - screenDensity: 2.1, - screenDpi: 320, - online: true, - charging: true, - lowMemory: true, - simulator: false, - memorySize: 1500, - freeMemory: 200, - usableMemory: 4294967296, - storageSize: 4294967296, - freeStorage: 2147483648, - externalStorageSize: 8589934592, - externalFreeStorage: 2863311530, - bootTime: DateTime.now().toUtc(), - ), - ), -); diff --git a/dart/example/bin/example.dart b/dart/example/bin/example.dart deleted file mode 100644 index d7530f5874..0000000000 --- a/dart/example/bin/example.dart +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:sentry/sentry.dart'; - -import 'event_example.dart'; - -/// Sends a test exception report to Sentry.io using this Dart client. -Future main() async { - // ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io - const dsn = - 'https://e85b375ffb9f43cf8bdf9787768149e0@o447951.ingest.sentry.io/5428562'; - - await Sentry.init( - (options) => options - ..dsn = dsn - ..debug = true - ..sendDefaultPii = true - ..addEventProcessor(TagEventProcessor()), - appRunner: runApp, - ); -} - -Future runApp() async { - print('\nReporting a complete event example: '); - - Sentry.addBreadcrumb( - Breadcrumb( - message: 'Authenticated user', - category: 'auth', - type: 'debug', - data: { - 'admin': true, - 'permissions': [1, 2, 3] - }, - ), - ); - - await Sentry.configureScope((scope) async { - await scope.setUser(SentryUser( - id: '800', - username: 'first-user', - email: 'first@user.lan', - // ipAddress: '127.0.0.1', sendDefaultPii feature is enabled - // ignore: deprecated_member_use - extras: {'first-sign-in': '2020-01-01'}, - )); - scope - // ..fingerprint = ['example-dart'], fingerprint forces events to group together - ..transaction = '/example/app' - ..level = SentryLevel.warning; - await scope.setTag('build', '579'); - await scope.setExtra('company-name', 'Dart Inc'); - }); - - // Sends a full Sentry event payload to show the different parts of the UI. - final sentryId = await Sentry.captureEvent(event); - - print('Capture event result : SentryId : $sentryId'); - - print('\nCapture message: '); - - // Sends a full Sentry event payload to show the different parts of the UI. - final messageSentryId = await Sentry.captureMessage( - 'Message 1', - level: SentryLevel.warning, - template: 'Message %s', - params: ['1'], - ); - - print('Capture message result : SentryId : $messageSentryId'); - - try { - await loadConfig(); - } catch (error, stackTrace) { - print('\nReporting the following stack trace: '); - print(stackTrace); - final sentryId = await Sentry.captureException( - error, - stackTrace: stackTrace, - ); - - print('Capture exception result : SentryId : $sentryId'); - } - - // capture unhandled error - await loadConfig(); -} - -Future loadConfig() async { - await parseConfig(); -} - -Future parseConfig() async { - await decode(); -} - -Future decode() async { - throw StateError('This is a test error'); -} - -class TagEventProcessor implements EventProcessor { - @override - SentryEvent? apply(SentryEvent event, hint) { - return event..tags?.addAll({'page-locale': 'en-us'}); - } -} diff --git a/dart/example/pubspec.yaml b/dart/example/pubspec.yaml deleted file mode 100644 index 4851622d44..0000000000 --- a/dart/example/pubspec.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: example -description: A simple command-line application. -version: 1.0.0 - -publish_to: none - -environment: - sdk: '>=3.5.0 <4.0.0' - -dependencies: - sentry: - path: ../../dart - -dev_dependencies: - lints: ^2.0.0 diff --git a/dart/example_web/.gitignore b/dart/example_web/.gitignore deleted file mode 100644 index 3d64647b50..0000000000 --- a/dart/example_web/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Files and directories created by pub -.dart_tool/ -.packages - -# Conventional directory for build outputs -build/ - -# Directory created by dartdoc -doc/api/ diff --git a/dart/example_web/README.md b/dart/example_web/README.md deleted file mode 100644 index 98202566f6..0000000000 --- a/dart/example_web/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Sentry Dart : web example - -```sh -dart pub get - -# run the project ( see https://dart.dev/tools/webdev#serve ) -dart run webdev serve --release -``` diff --git a/dart/example_web/analysis_options.yaml b/dart/example_web/analysis_options.yaml deleted file mode 100644 index be16ace7d1..0000000000 --- a/dart/example_web/analysis_options.yaml +++ /dev/null @@ -1,5 +0,0 @@ -include: package:lints/recommended.yaml - -analyzer: - errors: - path_does_not_exist: ignore diff --git a/dart/example_web/pubspec.yaml b/dart/example_web/pubspec.yaml deleted file mode 100644 index 5001acbef0..0000000000 --- a/dart/example_web/pubspec.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: sentry_dart_web_example -description: An absolute bare-bones web app. - -publish_to: 'none' - -environment: - sdk: '>=3.5.0 <4.0.0' - -dependencies: - sentry: - path: ../../dart/ - web: ^1.1.0 - -dev_dependencies: - build_runner: ^2.4.2 - build_web_compilers: ^4.0.3 - lints: ^2.0.0 - webdev: ^3.0.4 diff --git a/dart/example_web/web/event.dart b/dart/example_web/web/event.dart deleted file mode 100644 index 51f1be5cb7..0000000000 --- a/dart/example_web/web/event.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:sentry/src/protocol.dart'; - -final event = SentryEvent( - logger: 'main', - serverName: 'server.dart', - release: '1.4.0-preview.1', - environment: 'Test', - message: SentryMessage('This is an example Dart event.'), - tags: const {'project-id': '7371'}, - // ignore: deprecated_member_use, deprecated_member_use_from_same_package - extra: const {'section': '1'}, - // fingerprint: const ['example-dart'], - user: SentryUser( - id: '800', - username: 'first-user', - email: 'first@user.lan', - // ipAddress: '127.0.0.1', - data: {'first-sign-in': '2020-01-01'}, - ), - breadcrumbs: [ - Breadcrumb( - message: 'UI Lifecycle', - timestamp: DateTime.now().toUtc(), - category: 'ui.lifecycle', - type: 'navigation', - data: {'screen': 'MainActivity', 'state': 'created'}, - level: SentryLevel.info, - ) - ], - contexts: Contexts( - operatingSystem: SentryOperatingSystem( - name: 'Android', - version: '5.0.2', - build: 'LRX22G.P900XXS0BPL2', - kernelVersion: - 'Linux version 3.4.39-5726670 (dpi@SWHC3807) (gcc version 4.8 (GCC) ) #1 SMP PREEMPT Thu Dec 1 19:42:39 KST 2016', - rooted: false, - ), - runtimes: [SentryRuntime(name: 'ART', version: '5')], - app: SentryApp( - name: 'Example Dart App', - version: '1.42.0', - identifier: 'HGT-App-13', - build: '93785', - buildType: 'release', - deviceAppHash: '5afd3a6', - startTime: DateTime.now().toUtc(), - ), - browser: SentryBrowser(name: 'Firefox', version: '42.0.1'), - device: SentryDevice( - name: 'SM-P900', - family: 'SM-P900', - model: 'SM-P900 (LRX22G)', - modelId: 'LRX22G', - arch: 'armeabi-v7a', - batteryLevel: 99, - orientation: SentryOrientation.landscape, - manufacturer: 'samsung', - brand: 'samsung', - screenDensity: 2.1, - screenDpi: 320, - online: true, - charging: true, - lowMemory: true, - simulator: false, - memorySize: 1500, - freeMemory: 200, - usableMemory: 4294967296, - storageSize: 4294967296, - freeStorage: 2147483648, - externalStorageSize: 8589934592, - externalFreeStorage: 2863311530, - bootTime: DateTime.now().toUtc(), - ), - ), -); diff --git a/dart/example_web/web/favicon.ico b/dart/example_web/web/favicon.ico deleted file mode 100644 index 7ba349b3e628d2423d4a2ed217422a4722f73739..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3559 zcmV|z)fLATAcZDKyK$JdGY~s=NSr`PnS}BvP$+3A z8CpoogqBhg+p;Cg51fS9@izOF7~1r6zw|?g zDQ!X_8B4l7_wKH=QY>4NwW55uUP;#D-rxP7bI-kdjtU{9Dpi9&%XV3<*GkWK^P@NG zgWRw6Vb?`n$T_Evx_k{$?y0Rh-E#bYD?-UGV3Tc>$SdfYhb2dG)#K`(KPKx z4IwA0_p^z5A4{(AI%=BqUe-mpgFoo&TY*3Gu!0a29lR)aGV2dpEZ4z|Kc)+FUc-bN zHIDPB&TC8HnJ0tyG0*^nmzmQ?TnN+!QqapY^N|7@`F5AqbYw-`02pC0LNbv4yz60?w^9K&j_>533B&I%i9tFNIn5p2kb+@G0y43>@$)ns6>BLG63+2Wpepx zJ&v#ILasL(C%pe{n)2h>g2u-1wVpgKUaNE4V$J76NI&82+j&+}!O~12Z$~FRKK$`9 zx^J3f|L@(w z@^0VL;CU-=w^+ZF9FR4?4ODJ#62DZXnxe`qk)!2S9)0Z%YeH3TkE!aMNY!YE_0LhF z2ESF$qU+kcNYfp>Oq;_Knx0_qs&4=0WPdHW`-Qyher0=jx5gB?QhDMW+Qc1=t$k|< zt=eZtRI`&@>AfXtZFZz?wIfZ37txkUL?4_$0OBvSIr99C2j2UN)Ni@j77k#SApKPq z|7OZGK1&}QM-|70VjJzpQ8hDwD&8DI6m)83lM`v+s(Btdr*I>`(aIvtK1ZDD;A51L zClILKDAJgMZ)-X|x8@2VC+X9BJv40&^lN&j5M^{HDvl4q-~qts09^Y4!n4Ma6_Lw34kz1b@>qe;tZn9VPT9z@k+{b=Lo2to6L3;F~QIz4!D1T|P-qRdf7Z303(CYKm}t10))3j2!;|tzyS7gc;G1rFhS73B&NU|LN;}mYr{eivPfUF zdm~5DreHsX?W>bdsM|qmnE=2HBnZ`V2&GU0HiPHE4BB~d@G=O*FMxyW35}^c+*y^d zu=LHL8rmGaLUn`myIgTKc-?scBq8(@2<4?z0#?C(P6j}(1UFeFC{V&pSs-Nh`dIqC zkq_zKagZ2z+AcRzw=V!dgs?$W0)eov1WLdv*y|LWVW)c@2!awQQ^c0$7^MT+`37Is z%4jsE07!ol4_@%H1b}B@02vS}j=YN~fUrVwC4dzE;VS8yeRqJ(To9x$c>TNqWIDzpRz&Sr zPzjP57~P9Na0}*O4%=_+^52#;fi&rNW3NA+l7688GL>)?AiTgTsszmeR~7(L6O~|@ zzz|qG+3C{n4%C4}E>qpUB(Ws{kV9bm(b{8HL<58sjR2ud0W;XQkP4(=2|ILf=2+pq z(O1(09&`AwG{n*Q)qw$JVxnF zMFb%C2^hk0fN(%m0*265LNmZ)!wN7*KLbbq8UaA{1auJa2wp!^`o#huDPc4NLNR?p zE@mJB=mh`=BfnEomf&3wBwPRh_zkhFA1nrdt00_4bi2$P+KLn!cjN=0CupO3Leg$3 zp*Vm{2>k+tq!Nk%A+NXX^~lmZ}E0)ru(A`q6O1aeT4#SAh5kY%uwe*{*64`?9{h|TK{lms9t zVMO!^gQrlLafwQR&uH5D+yIa;xWn}w$_&dP-ZmCH63kNx)pmez0+e9HK7lI?Lbe@Z zCIIH03!8~Gbn zf+p*Bct|+_8A_;n`y?vsWCSI&<*x)yyDR;;ESm|WDWSu=9V-Fv4K$Kt?D8OWhX~-< z8M4JKx(QsRgh2tq34qYWSpHUUkm|e@h>8u?io3kMt+jNkPo$fU+`TO^E$=_ zAV@2L(Nh=zdBX|I7zlv)vLWhvxn(AR^nQB+a(@#wUK`rQ52NkQchOw{V?Bles;Gnx zuO~1Di)SVo=CHckmenU{((WCK0PvY$@A#*1=j-)CbAeSgo{@WXVb|Yr24@501Of;Q zgQUdn@s6RV_;ctHhZSwHy^XM+5McC+FpA(acq zkST#cFbNRUG6bnF(C#1)tpLs{oldkvBx7pL^j%9 z^aQ|o(0&Tt4lvfjK-P*ds`G^*Gl%u3PGSg&Ms9I z*zZ)`R3{W-EGbbsnIz4z4?~&D2QBA=kRHntC1hrXOE4OI7(xn09lZ7ozLsW{b=7 zbnCtL2cfv(eDh3zWQflPAv+AgOlsk^pSVZR4(AZM7hvEebZwgR987~DJRT$~4t`JN z@IV4P-6z6hXeZ}5TxI0SRjTv?3$ouKS*60hr&tvtLe{uv^Z_W4m}z-GL@GnHGIPk* zw6ctFod^P(OD!y`KXwnJ@4>QqH;FL@i7G0^fC~dyCpy$y;qkr9N%VyCOuRPafGQLB zzxU5Nx5-m}$bfT6kttLODx@M`to1wZ2XmNi7JNd^g%aAUV6e$$mBbisA;#D$#u!)` zw}J0?$bOnExiyeYuJhSrI5vUQ{Xnh5v4#|I^i3@pb{W7_{P2k5GK==kbAYr zd@D&R#;~Cu!m^6Z1Sv9BK^_RF-@KuRkuuEQ=LX6u&}L20<6F-P1JfjkL^$kk*d@$ZG_p zlDS-4dId>x;8Ix))Ft8KEW?C11O-;*xfWL`Qzk1{Ldf+^h!aB1=lxg-30(gpl+6{; zlAp7sn($go>tSNJPRTIkIh2%t4%H;e)d~Xy$^IHbwmS{eULGp}7eC>K>x%RdXHl9i z=pa>P`f>La2+w!sQ%|I9!8C>-&H_}9-U;=8E{GN8praR|_~}w{8h=S2<}S6&1}__C z{K0ykqcUgtgVR>NYFus(0ow+ctv$LRyQjfxf3DtV-(8H>5U@W7MVi`%u=AlE% - - - - - - - - - - - dart_web - - - - - - - - - - -
- -
- -
Captured
-
- -
- -
Captured
-
- -
- -
Captured
-
- -
- -
Captured
-
- - - diff --git a/dart/example_web/web/main.dart b/dart/example_web/web/main.dart deleted file mode 100644 index 7e349b7276..0000000000 --- a/dart/example_web/web/main.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'dart:async'; -import 'package:web/web.dart'; - -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/version.dart'; - -import 'event.dart'; - -// ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io -const dsn = - 'https://e85b375ffb9f43cf8bdf9787768149e0@o447951.ingest.sentry.io/5428562'; - -Future main() async { - await Sentry.init( - (options) => options - ..dsn = dsn - ..debug = true - ..sendDefaultPii = true - ..addEventProcessor(TagEventProcessor()), - appRunner: runApp, - ); -} - -Future runApp() async { - print('runApp'); - - // ignore: deprecated_member_use - document.querySelector('#output')?.text = 'Your Dart app is running.'; - - await Sentry.addBreadcrumb( - Breadcrumb( - message: 'Authenticated user', - category: 'auth', - type: 'debug', - data: { - 'admin': true, - 'permissions': [1, 2, 3] - }, - ), - ); - - await Sentry.configureScope((scope) async { - scope - // ..fingerprint = ['example-dart'] - ..transaction = '/example/app' - ..level = SentryLevel.warning; - await scope.setTag('build', '579'); - // ignore: deprecated_member_use - await scope.setExtra('company-name', 'Dart Inc'); - - await scope.setUser( - SentryUser( - id: '800', - username: 'first-user', - email: 'first@user.lan', - // ipAddress: '127.0.0.1', - data: {'first-sign-in': '2020-01-01'}, - ), - ); - }); - - document - .querySelector('#btEvent') - ?.onClick - .listen((event) => captureCompleteExampleEvent()); - document - .querySelector('#btMessage') - ?.onClick - .listen((event) => captureMessage()); - document - .querySelector('#btException') - ?.onClick - .listen((event) => captureException()); - document - .querySelector('#btUnhandledException') - ?.onClick - .listen((event) => captureUnhandledException()); -} - -Future captureMessage() async { - print('Capturing Message : '); - final sentryId = await Sentry.captureMessage( - 'Message 2', - template: 'Message %s', - params: ['2'], - ); - print('capture message result : $sentryId'); - if (sentryId != SentryId.empty()) { - (document.querySelector('#messageResult') as HTMLElement?)?.style.display = - 'block'; - } -} - -Future captureException() async { - try { - await buildCard(); - } catch (error, stackTrace) { - print('\nReporting the following stack trace: '); - final sentryId = await Sentry.captureException( - error, - stackTrace: stackTrace, - ); - - print('Capture exception : SentryId: $sentryId'); - - if (sentryId != SentryId.empty()) { - (document.querySelector('#exceptionResult') as HTMLElement?) - ?.style - .display = 'block'; - } - } -} - -Future captureUnhandledException() async { - (document.querySelector('#unhandledResult') as HTMLElement?)?.style.display = - 'block'; - - await buildCard(); -} - -Future captureCompleteExampleEvent() async { - print('\nReporting a complete event example: $sdkName'); - final sentryId = await Sentry.captureEvent(event); - - print('Response SentryId: $sentryId'); - - if (sentryId != SentryId.empty()) { - (document.querySelector('#eventResult') as HTMLElement?)?.style.display = - 'block'; - } -} - -Future buildCard() async { - await loadData(); -} - -Future loadData() async { - await parseData(); -} - -Future parseData() async { - throw StateError('This is a test error'); -} - -class TagEventProcessor implements EventProcessor { - @override - SentryEvent? apply(SentryEvent event, Hint hint) { - return event..tags?.addAll({'page-locale': 'en-us'}); - } -} diff --git a/dart/example_web/web/styles.css b/dart/example_web/web/styles.css deleted file mode 100644 index cc035c95c9..0000000000 --- a/dart/example_web/web/styles.css +++ /dev/null @@ -1,14 +0,0 @@ -@import url(https://fonts.googleapis.com/css?family=Roboto); - -html, body { - width: 100%; - height: 100%; - margin: 0; - padding: 0; - font-family: 'Roboto', sans-serif; -} - -#output { - padding: 20px; - text-align: center; -} diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart deleted file mode 100644 index bb349a6a3c..0000000000 --- a/dart/lib/sentry.dart +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/// A pure Dart client for Sentry.io crash reporting. -library; - -// ignore: invalid_export_of_internal_element -export 'src/constants.dart'; -export 'src/event_processor.dart'; -export 'src/exception_cause.dart'; -export 'src/exception_cause_extractor.dart'; -export 'src/exception_stacktrace_extractor.dart'; -export 'src/exception_type_identifier.dart'; -export 'src/hint.dart'; -export 'src/http_client/sentry_http_client.dart'; -export 'src/http_client/sentry_http_client_error.dart'; -export 'src/hub.dart'; -export 'src/hub_adapter.dart'; -export 'src/integration.dart'; -export 'src/noop_isolate_error_integration.dart' - if (dart.library.io) 'src/isolate_error_integration.dart'; -// ignore: invalid_export_of_internal_element -export 'src/performance_collector.dart'; -export 'src/protocol.dart'; -export 'src/protocol/sentry_feature_flag.dart'; -export 'src/protocol/sentry_feature_flags.dart'; -export 'src/protocol/sentry_feedback.dart'; -export 'src/protocol/sentry_proxy.dart'; -export 'src/run_zoned_guarded_integration.dart'; -export 'src/runtime_checker.dart'; -export 'src/scope.dart'; -export 'src/scope_observer.dart'; -export 'src/sentry.dart'; -export 'src/sentry_attachment/sentry_attachment.dart'; -export 'src/sentry_baggage.dart'; -// ignore: invalid_export_of_internal_element -export 'src/sentry_client.dart'; -// ignore: invalid_export_of_internal_element -export 'src/sdk_lifecycle_hooks.dart'; -export 'src/sentry_envelope.dart'; -export 'src/sentry_envelope_item.dart'; -export 'src/sentry_options.dart'; -// ignore: invalid_export_of_internal_element -export 'src/sentry_trace_origins.dart'; -export 'src/span_data_convention.dart'; -export 'src/spotlight.dart'; -export 'src/throwable_mechanism.dart'; -export 'src/tracing.dart'; -export 'src/transport/transport.dart'; -export 'src/type_check_hint.dart'; -// ignore: invalid_export_of_internal_element -export 'src/utils.dart'; -// ignore: invalid_export_of_internal_element -export 'src/utils/http_header_utils.dart'; -// ignore: invalid_export_of_internal_element -export 'src/utils/http_sanitizer.dart'; -export 'src/utils/tracing_utils.dart'; -// ignore: invalid_export_of_internal_element -export 'src/utils/url_details.dart'; -// ignore: invalid_export_of_internal_element -export 'src/utils/breadcrumb_log_level.dart'; -export 'src/sentry_logger.dart'; diff --git a/dart/lib/sentry_io.dart b/dart/lib/sentry_io.dart deleted file mode 100644 index 2c8eb91bde..0000000000 --- a/dart/lib/sentry_io.dart +++ /dev/null @@ -1,6 +0,0 @@ -// ignore: invalid_export_of_internal_element -export 'sentry.dart'; -export 'src/sentry_attachment/io_sentry_attachment.dart'; -// Isolates -export 'src/sentry_isolate_extension.dart'; -export 'src/sentry_isolate.dart'; diff --git a/dart/lib/src/client_reports/client_report.dart b/dart/lib/src/client_reports/client_report.dart deleted file mode 100644 index 347f8c7624..0000000000 --- a/dart/lib/src/client_reports/client_report.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../utils.dart'; -import 'discarded_event.dart'; - -@internal -class ClientReport { - ClientReport(this.timestamp, this.discardedEvents); - - final DateTime? timestamp; - final List discardedEvents; - - Map toJson() { - final json = {}; - - if (timestamp != null) { - json['timestamp'] = formatDateAsIso8601WithMillisPrecision(timestamp!); - } - - final eventsJson = discardedEvents - .map((e) => e.toJson()) - .where((e) => e.isNotEmpty) - .toList(growable: false); - if (eventsJson.isNotEmpty) { - json['discarded_events'] = eventsJson; - } - - return json; - } -} diff --git a/dart/lib/src/client_reports/client_report_recorder.dart b/dart/lib/src/client_reports/client_report_recorder.dart deleted file mode 100644 index 045168f0c5..0000000000 --- a/dart/lib/src/client_reports/client_report_recorder.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../sentry_options.dart'; -import 'client_report.dart'; -import 'discarded_event.dart'; -import 'discard_reason.dart'; -import '../transport/data_category.dart'; - -@internal -class ClientReportRecorder { - ClientReportRecorder(this._clock); - - final ClockProvider _clock; - final Map<_QuantityKey, int> _quantities = {}; - - void recordLostEvent(final DiscardReason reason, final DataCategory category, - {int count = 1}) { - final key = _QuantityKey(reason, category); - var current = _quantities[key] ?? 0; - _quantities[key] = current + count; - } - - ClientReport? flush() { - if (_quantities.isEmpty) { - return null; - } - - final events = _quantities.keys.map((key) { - final quantity = _quantities[key] ?? 0; - return DiscardedEvent(key.reason, key.category, quantity); - }).toList(growable: false); - - _quantities.clear(); - - return ClientReport(_clock(), events); - } -} - -class _QuantityKey { - _QuantityKey(this.reason, this.category); - - final DiscardReason reason; - final DataCategory category; - - @override - int get hashCode => Object.hash(reason.hashCode, category.hashCode); - - @override - // ignore: non_nullable_equals_parameter - bool operator ==(dynamic other) { - return other is _QuantityKey && - other.reason == reason && - other.category == category; - } -} diff --git a/dart/lib/src/client_reports/discard_reason.dart b/dart/lib/src/client_reports/discard_reason.dart deleted file mode 100644 index df4de4b64c..0000000000 --- a/dart/lib/src/client_reports/discard_reason.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:meta/meta.dart'; - -/// A reason that defines why events were lost, see -/// https://develop.sentry.dev/sdk/client-reports/#envelope-item-payload. -@internal -enum DiscardReason { - beforeSend, - eventProcessor, - sampleRate, - networkError, - queueOverflow, - cacheOverflow, - rateLimitBackoff, - ignored, -} diff --git a/dart/lib/src/client_reports/discarded_event.dart b/dart/lib/src/client_reports/discarded_event.dart deleted file mode 100644 index 24a3471df0..0000000000 --- a/dart/lib/src/client_reports/discarded_event.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'discard_reason.dart'; -import '../transport/data_category.dart'; - -@internal -class DiscardedEvent { - DiscardedEvent(this.reason, this.category, this.quantity); - - final DiscardReason reason; - final DataCategory category; - final int quantity; - - Map toJson() { - return { - 'reason': reason._toStringValue(), - 'category': category._toStringValue(), - 'quantity': quantity, - }; - } -} - -extension _OutcomeExtension on DiscardReason { - String _toStringValue() { - switch (this) { - case DiscardReason.beforeSend: - return 'before_send'; - case DiscardReason.eventProcessor: - return 'event_processor'; - case DiscardReason.sampleRate: - return 'sample_rate'; - case DiscardReason.networkError: - return 'network_error'; - case DiscardReason.queueOverflow: - return 'queue_overflow'; - case DiscardReason.cacheOverflow: - return 'cache_overflow'; - case DiscardReason.rateLimitBackoff: - return 'ratelimit_backoff'; - case DiscardReason.ignored: - return 'ignored'; - } - } -} - -extension _DataCategoryExtension on DataCategory { - String _toStringValue() { - switch (this) { - case DataCategory.all: - return '__all__'; - case DataCategory.dataCategoryDefault: - return 'default'; - case DataCategory.error: - return 'error'; - case DataCategory.session: - return 'session'; - case DataCategory.transaction: - return 'transaction'; - case DataCategory.span: - return 'span'; - case DataCategory.attachment: - return 'attachment'; - case DataCategory.security: - return 'security'; - case DataCategory.unknown: - return 'unknown'; - case DataCategory.logItem: - return 'log_item'; - case DataCategory.feedback: - return 'feedback'; - case DataCategory.metricBucket: - return 'metric_bucket'; - } - } -} diff --git a/dart/lib/src/client_reports/noop_client_report_recorder.dart b/dart/lib/src/client_reports/noop_client_report_recorder.dart deleted file mode 100644 index b03bd9c9ef..0000000000 --- a/dart/lib/src/client_reports/noop_client_report_recorder.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../transport/data_category.dart'; -import 'client_report.dart'; -import 'client_report_recorder.dart'; -import 'discard_reason.dart'; - -@internal -class NoOpClientReportRecorder implements ClientReportRecorder { - const NoOpClientReportRecorder(); - - @override - ClientReport? flush() { - return null; - } - - @override - void recordLostEvent(DiscardReason reason, DataCategory category, - {int count = 1}) {} -} diff --git a/dart/lib/src/constants.dart b/dart/lib/src/constants.dart deleted file mode 100644 index caa7641db0..0000000000 --- a/dart/lib/src/constants.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:meta/meta.dart'; - -@internal -class SentrySpanOperations { - static const String uiLoad = 'ui.load'; - static const String uiTimeToInitialDisplay = 'ui.load.initial_display'; - static const String uiTimeToFullDisplay = 'ui.load.full_display'; - static const String dbSqlQuery = 'db.sql.query'; - static const String dbSqlTransaction = 'db.sql.transaction'; - static const String dbSqlBatch = 'db.sql.batch'; - static const String dbOpen = 'db.open'; - static const String dbClose = 'db.close'; -} - -@internal -class SentrySpanData { - static const String dbSystemKey = 'db.system'; - static const String dbNameKey = 'db.name'; - - static const String dbSystemSqlite = 'db.sqlite'; -} - -@internal -class SentrySpanDescriptions { - static const String dbTransaction = 'Transaction'; - static String dbBatch({required List statements}) => - 'Batch $statements'; - static String dbOpen({required String dbName}) => 'Open database $dbName'; - static String dbClose({required String dbName}) => 'Close database $dbName'; -} diff --git a/dart/lib/src/dart_exception_type_identifier.dart b/dart/lib/src/dart_exception_type_identifier.dart deleted file mode 100644 index 9f165ccba4..0000000000 --- a/dart/lib/src/dart_exception_type_identifier.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'dart:async' show TimeoutException, AsyncError, DeferredLoadException; - -import 'package:http/http.dart' show ClientException; - -import '../sentry.dart'; -import 'dart_exception_type_identifier_io.dart' - if (dart.library.js_interop) 'dart_exception_type_identifier_web.dart'; - -class DartExceptionTypeIdentifier implements ExceptionTypeIdentifier { - @override - String? identifyType(dynamic throwable) { - // dart:core - if (throwable is ArgumentError) return 'ArgumentError'; - if (throwable is AssertionError) return 'AssertionError'; - if (throwable is ConcurrentModificationError) { - return 'ConcurrentModificationError'; - } - if (throwable is FormatException) return 'FormatException'; - if (throwable is IndexError) return 'IndexError'; - if (throwable is NoSuchMethodError) return 'NoSuchMethodError'; - if (throwable is OutOfMemoryError) return 'OutOfMemoryError'; - if (throwable is RangeError) return 'RangeError'; - if (throwable is StackOverflowError) return 'StackOverflowError'; - if (throwable is StateError) return 'StateError'; - if (throwable is TypeError) return 'TypeError'; - if (throwable is UnimplementedError) return 'UnimplementedError'; - if (throwable is UnsupportedError) return 'UnsupportedError'; - // not adding Exception or Error because it's too generic - - // dart:async - if (throwable is TimeoutException) return 'TimeoutException'; - if (throwable is AsyncError) return 'FutureTimeout'; - if (throwable is DeferredLoadException) return 'DeferredLoadException'; - // not adding ParallelWaitError because it's not supported in dart 2.17.0 - - // dart http package - if (throwable is ClientException) return 'ClientException'; - - // platform specific exceptions - return identifyPlatformSpecificException(throwable); - } -} diff --git a/dart/lib/src/dart_exception_type_identifier_io.dart b/dart/lib/src/dart_exception_type_identifier_io.dart deleted file mode 100644 index 1945663a01..0000000000 --- a/dart/lib/src/dart_exception_type_identifier_io.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:io'; - -import 'package:meta/meta.dart'; - -@internal -String? identifyPlatformSpecificException(dynamic throwable) { - if (throwable is FileSystemException) return 'FileSystemException'; - if (throwable is HttpException) return 'HttpException'; - if (throwable is SocketException) return 'SocketException'; - if (throwable is HandshakeException) return 'HandshakeException'; - if (throwable is CertificateException) return 'CertificateException'; - if (throwable is TlsException) return 'TlsException'; - return null; -} diff --git a/dart/lib/src/dart_exception_type_identifier_web.dart b/dart/lib/src/dart_exception_type_identifier_web.dart deleted file mode 100644 index 088ce9556e..0000000000 --- a/dart/lib/src/dart_exception_type_identifier_web.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:meta/meta.dart'; - -@internal -String? identifyPlatformSpecificException(dynamic throwable) { - return null; -} diff --git a/dart/lib/src/diagnostic_log.dart b/dart/lib/src/diagnostic_log.dart deleted file mode 100644 index 2379dafbc1..0000000000 --- a/dart/lib/src/diagnostic_log.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'protocol.dart'; -import 'sentry_options.dart'; - -class DiagnosticLog { - final SentryOptions _options; - final SdkLogCallback _logger; - SdkLogCallback get logger => _logger; - - DiagnosticLog(this._logger, this._options); - - void log( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - if (_isEnabled(level)) { - _logger( - level, - message, - logger: logger, - exception: exception, - stackTrace: stackTrace, - ); - } - } - - bool _isEnabled(SentryLevel level) { - return _options.debug && - level.ordinal >= _options.diagnosticLevel.ordinal || - level == SentryLevel.fatal; - } -} diff --git a/dart/lib/src/environment/_io_environment_variables.dart b/dart/lib/src/environment/_io_environment_variables.dart deleted file mode 100644 index 93389afb83..0000000000 --- a/dart/lib/src/environment/_io_environment_variables.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'dart:io'; - -import '_web_environment_variables.dart'; -import 'environment_variables.dart'; -import 'keys.dart'; - -final EnvironmentVariables envs = IoEnvironmentVariables(); - -/// In addition to dart defines this io implementation can read from the -/// environment variables. -class IoEnvironmentVariables extends WebEnvironmentVariables { - @override - String? get environment => - super.environment ?? Platform.environment[sentryEnvironment]; - - @override - String? get dsn => super.dsn ?? Platform.environment[sentryDsn]; - - @override - String? get release => super.release ?? Platform.environment[sentryRelease]; - - @override - String? get dist => super.dist ?? Platform.environment[sentryDist]; -} diff --git a/dart/lib/src/environment/_web_environment_variables.dart b/dart/lib/src/environment/_web_environment_variables.dart deleted file mode 100644 index 8721464a78..0000000000 --- a/dart/lib/src/environment/_web_environment_variables.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'environment_variables.dart'; -import 'keys.dart'; - -final EnvironmentVariables envs = WebEnvironmentVariables(); - -class WebEnvironmentVariables extends EnvironmentVariables { - @override - String? get environment => const bool.hasEnvironment(sentryEnvironment) - ? const String.fromEnvironment(sentryEnvironment) - : null; - - @override - String? get dsn => const bool.hasEnvironment(sentryDsn) - ? const String.fromEnvironment(sentryDsn) - : null; - - @override - String? get release => const bool.hasEnvironment(sentryRelease) - ? const String.fromEnvironment(sentryRelease) - : null; - - @override - String? get dist => const bool.hasEnvironment(sentryDist) - ? const String.fromEnvironment(sentryDist) - : null; -} diff --git a/dart/lib/src/environment/environment_variables.dart b/dart/lib/src/environment/environment_variables.dart deleted file mode 100644 index 51c8b1ea0d..0000000000 --- a/dart/lib/src/environment/environment_variables.dart +++ /dev/null @@ -1,43 +0,0 @@ -import '../runtime_checker.dart'; -import '_io_environment_variables.dart' - if (dart.library.js_interop) '_web_environment_variables.dart' as env; - -/// Reads environment variables from the system. -/// In an Flutter environment these can be set via -/// `flutter build --dart-define=VARIABLE_NAME=VARIABLE_VALUE`. -abstract class EnvironmentVariables { - factory EnvironmentVariables.instance() => env.envs; - - const EnvironmentVariables(); - - /// `SENTRY_ENVIRONMENT` - /// See [SentryOptions.environment] - String? get environment; - - /// `SENTRY_DSN` - /// See [SentryOptions.dsn] - String? get dsn; - - /// `SENTRY_RELEASE` - /// See [SentryOptions.release] - String? get release; - - /// `SENTRY_DIST` - /// See [SentryOptions.dist] - String? get dist; - - /// Returns an environment based on the compilation mode of Dart or Flutter. - /// This can be set as [SentryOptions.environment] - String environmentForMode(RuntimeChecker checker) { - // We infer the environment based on the release/non-release and profile - // constants. - - if (checker.isReleaseMode()) { - return 'production'; - } - if (checker.isProfileMode()) { - return 'profile'; - } - return 'debug'; - } -} diff --git a/dart/lib/src/environment/keys.dart b/dart/lib/src/environment/keys.dart deleted file mode 100644 index 8ab8a0a11c..0000000000 --- a/dart/lib/src/environment/keys.dart +++ /dev/null @@ -1,6 +0,0 @@ -// These are the keys which have to be set as environment variables or Dart -// defines in order to configure the corresponding Sentry setting -const sentryEnvironment = 'SENTRY_ENVIRONMENT'; -const sentryDsn = 'SENTRY_DSN'; -const sentryRelease = 'SENTRY_RELEASE'; -const sentryDist = 'SENTRY_DIST'; diff --git a/dart/lib/src/event_processor.dart b/dart/lib/src/event_processor.dart deleted file mode 100644 index 45005d7b0e..0000000000 --- a/dart/lib/src/event_processor.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:async'; - -import 'hint.dart'; -import 'protocol.dart'; - -/// [EventProcessor]s are callbacks that run for every event. They can either -/// return a new event which in most cases means just adding data *or* return -/// null in case the event will be dropped and not sent. -abstract class EventProcessor { - FutureOr apply( - SentryEvent event, - Hint hint, - ); -} diff --git a/dart/lib/src/event_processor/deduplication_event_processor.dart b/dart/lib/src/event_processor/deduplication_event_processor.dart deleted file mode 100644 index 48661257a6..0000000000 --- a/dart/lib/src/event_processor/deduplication_event_processor.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'dart:collection'; -import '../event_processor.dart'; -import '../hint.dart'; -import '../protocol.dart'; -import '../sentry_options.dart'; - -/// Deduplicates events with the same [SentryEvent.throwable]. -/// It keeps track of the last [SentryOptions.maxDeduplicationItems] -/// events. Older events aren't considered for deduplication. -/// -/// Only [SentryEvent]s where [SentryEvent.throwable] is not null are considered -/// for deduplication. [SentryEvent]s without exceptions aren't deduplicated. -/// -/// Caveats: -/// It does not work in the following case: -/// ```dart -/// var fooOne = Exception('foo'); -/// var fooTwo = Exception('foo'); -/// ``` -/// because (fooOne == fooTwo) equals false -class DeduplicationEventProcessor implements EventProcessor { - DeduplicationEventProcessor(this._options); - - // Using a HashSet makes this performant. - final Queue _exceptionToDeduplicate = Queue(); - final SentryOptions _options; - - @override - SentryEvent? apply(SentryEvent event, Hint hint) { - if (event is SentryTransaction) { - return event; - } - - if (!_options.enableDeduplication) { - _options.log(SentryLevel.debug, 'Deduplication is disabled'); - return event; - } - return _deduplicate(event); - } - - SentryEvent? _deduplicate(SentryEvent event) { - // Cast to `Object?` in order to enable better type checking - // because `event.throwable` is `dynamic` - final exception = event.throwable as Object?; - - if (exception == null) { - // If no exception is given, just return the event - return event; - } - - // Just use the hashCode, to keep the memory footprint small - final exceptionHashCode = exception.hashCode; - - if (_exceptionToDeduplicate.contains(exceptionHashCode)) { - _options.log( - SentryLevel.info, - 'Duplicated exception detected. ' - 'Event ${event.eventId} will be discarded.', - ); - return null; - } - - // No duplication detected - _exceptionToDeduplicate.add(exceptionHashCode); - if (_exceptionToDeduplicate.length > _options.maxDeduplicationItems) { - _exceptionToDeduplicate.removeFirst(); - } - return event; - } -} diff --git a/dart/lib/src/event_processor/enricher/enricher_event_processor.dart b/dart/lib/src/event_processor/enricher/enricher_event_processor.dart deleted file mode 100644 index 2b42b8f043..0000000000 --- a/dart/lib/src/event_processor/enricher/enricher_event_processor.dart +++ /dev/null @@ -1,9 +0,0 @@ -import '../../event_processor.dart'; -import '../../sentry_options.dart'; -import 'io_enricher_event_processor.dart' - if (dart.library.js_interop) 'web_enricher_event_processor.dart'; - -abstract class EnricherEventProcessor implements EventProcessor { - factory EnricherEventProcessor(SentryOptions options) => - enricherEventProcessor(options); -} diff --git a/dart/lib/src/event_processor/enricher/flutter_runtime.dart b/dart/lib/src/event_processor/enricher/flutter_runtime.dart deleted file mode 100644 index 9c7a905194..0000000000 --- a/dart/lib/src/event_processor/enricher/flutter_runtime.dart +++ /dev/null @@ -1,76 +0,0 @@ -import '../../protocol/sentry_runtime.dart'; - -// The Flutter version information can be fetched via Dart defines, -// see -// - https://github.com/flutter/flutter/pull/140783 -// - https://github.com/flutter/flutter/pull/163761 -// -// This code lives in the Dart only Sentry code, since the code -// doesn't require any Flutter dependency. -// Additionally, this makes it work on background isolates in -// Flutter, where one may not initialize the whole Flutter Sentry -// SDK. -// The const-ness of the properties below ensure that the code -// is tree shaken in a non-Flutter environment. - -const _isFlutterRuntimeInformationAbsent = FlutterVersion.version == null || - FlutterVersion.channel == null || - FlutterVersion.frameworkRevision == null; - -final SentryRuntime? flutterRuntime = _isFlutterRuntimeInformationAbsent - ? null - : SentryRuntime( - name: 'Flutter', - version: '${FlutterVersion.version} (${FlutterVersion.channel})', - build: FlutterVersion.frameworkRevision, - rawDescription: '${FlutterVersion.version} (${FlutterVersion.channel}) ' - '- Git hash ${FlutterVersion.frameworkRevision} ' - '- Git URL ${FlutterVersion.gitUrl}', - ); - -final SentryRuntime? dartFlutterRuntime = FlutterVersion.dartVersion == null - ? null - : SentryRuntime(name: 'Dart', version: FlutterVersion.dartVersion); - -/// Details about the Flutter version this app was compiled with, -/// corresponding to the output of `flutter --version`. -/// -/// When this Flutter version was build from a fork, or when Flutter runs in a -/// custom embedder, these values might be unreliable. -abstract class FlutterVersion { - const FlutterVersion._(); - - /// The Flutter version used to compile the app. - static const String? version = bool.hasEnvironment('FLUTTER_VERSION') - ? String.fromEnvironment('FLUTTER_VERSION') - : null; - - /// The Flutter channel used to compile the app. - static const String? channel = bool.hasEnvironment('FLUTTER_CHANNEL') - ? String.fromEnvironment('FLUTTER_CHANNEL') - : null; - - /// The URL of the Git repository from which Flutter was obtained. - static const String? gitUrl = bool.hasEnvironment('FLUTTER_GIT_URL') - ? String.fromEnvironment('FLUTTER_GIT_URL') - : null; - - /// The Flutter framework revision, as a (short) Git commit ID. - static const String? frameworkRevision = - bool.hasEnvironment('FLUTTER_FRAMEWORK_REVISION') - ? String.fromEnvironment('FLUTTER_FRAMEWORK_REVISION') - : null; - - /// The Flutter engine revision. - static const String? engineRevision = - bool.hasEnvironment('FLUTTER_ENGINE_REVISION') - ? String.fromEnvironment('FLUTTER_ENGINE_REVISION') - : null; - - // This is included since [Platform.version](https://api.dart.dev/stable/dart-io/Platform/version.html) - // is not included on web platforms. - /// The Dart version used to compile the app. - static const String? dartVersion = bool.hasEnvironment('FLUTTER_DART_VERSION') - ? String.fromEnvironment('FLUTTER_DART_VERSION') - : null; -} diff --git a/dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart b/dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart deleted file mode 100644 index 288d047700..0000000000 --- a/dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart +++ /dev/null @@ -1,142 +0,0 @@ -import 'dart:io'; - -import '../../../sentry.dart'; -import 'enricher_event_processor.dart'; -import 'flutter_runtime.dart'; -import 'io_platform_memory.dart'; -import '../../utils/os_utils.dart'; - -EnricherEventProcessor enricherEventProcessor(SentryOptions options) { - return IoEnricherEventProcessor(options); -} - -/// Enriches [SentryEvent]s with various kinds of information. -/// Uses Darts [Platform](https://api.dart.dev/stable/dart-io/Platform-class.html) -/// class to read information. -class IoEnricherEventProcessor implements EnricherEventProcessor { - IoEnricherEventProcessor(this._options); - - final SentryOptions _options; - late final String _dartVersion = _extractDartVersion(Platform.version); - late final SentryOperatingSystem _os = getSentryOperatingSystem(); - bool _fetchedTotalPhysicalMemory = false; - int? _totalPhysicalMemory; - - /// Extracts the semantic version and channel from the full version string. - /// - /// Example: - /// Input: "3.5.0-180.3.beta (beta) (Wed Jun 5 15:06:15 2024 +0000) on "android_arm64"" - /// Output: "3.5.0-180.3.beta (beta)" - /// - /// Falls back to the full version if the matching fails. - String _extractDartVersion(String fullVersion) { - RegExp channelRegex = RegExp(r'\((stable|beta|dev)\)'); - Match? match = channelRegex.firstMatch(fullVersion); - // if match is null this will return the full version - return fullVersion.substring(0, match?.end); - } - - @override - Future apply(SentryEvent event, Hint hint) async { - event.contexts - ..device = await _getDevice(event.contexts.device) - ..operatingSystem = _getOperatingSystem(event.contexts.operatingSystem) - ..runtimes = _getRuntimes(event.contexts.runtimes) - ..app = _getApp(event.contexts.app) - ..culture = _getSentryCulture(event.contexts.culture); - - event.contexts['dart_context'] = _getDartContext(); - return event; - } - - List _getRuntimes(List? runtimes) { - // Pure Dart doesn't have specific runtimes per build mode - // like Flutter: https://flutter.dev/docs/testing/build-modes - final dartRuntime = SentryRuntime( - name: 'Dart', - version: _dartVersion, - rawDescription: Platform.version, - ); - final flRuntime = flutterRuntime; - - if (runtimes == null) { - return [dartRuntime, if (flRuntime != null) flRuntime]; - } - return [ - ...runtimes, - dartRuntime, - if (flRuntime != null) flRuntime, - ]; - } - - Map _getDartContext() { - final args = Platform.executableArguments; - final packageConfig = Platform.packageConfig; - - String? executable; - if (_options.sendDefaultPii) { - executable = Platform.executable; - } - - return { - 'compile_mode': _options.runtimeChecker.compileMode, - if (packageConfig != null) 'package_config': packageConfig, - // The following information could potentially contain PII - if (_options.sendDefaultPii) ...{ - 'executable': executable, - 'resolved_executable': Platform.resolvedExecutable, - 'script': Platform.script.toString(), - if (args.isNotEmpty) - 'executable_arguments': Platform.executableArguments, - }, - }; - } - - Future _getDevice(SentryDevice? device) async { - device ??= SentryDevice(); - return device - ..name = device.name ?? - (_options.sendDefaultPii ? Platform.localHostname : null) - ..processorCount = device.processorCount ?? Platform.numberOfProcessors - ..memorySize = device.memorySize ?? await _getTotalPhysicalMemory() - ..freeMemory = device.freeMemory; - } - - Future _getTotalPhysicalMemory() async { - if (!_fetchedTotalPhysicalMemory) { - _totalPhysicalMemory = - await PlatformMemory(_options).getTotalPhysicalMemory(); - _fetchedTotalPhysicalMemory = true; - } - return _totalPhysicalMemory; - } - - SentryApp _getApp(SentryApp? app) { - app ??= SentryApp(); - return app..appMemory = app.appMemory ?? ProcessInfo.currentRss; - } - - SentryOperatingSystem _getOperatingSystem(SentryOperatingSystem? os) { - if (os == null) { - return SentryOperatingSystem( - name: _os.name, - version: _os.version, - build: _os.build, - kernelVersion: _os.kernelVersion, - rooted: _os.rooted, - rawDescription: _os.rawDescription, - theme: _os.theme, - unknown: _os.unknown, - ); - } else { - return _os.mergeWith(os); - } - } - - SentryCulture _getSentryCulture(SentryCulture? culture) { - culture ??= SentryCulture(); - return culture - ..locale = culture.locale ?? Platform.localeName - ..timezone = culture.timezone ?? DateTime.now().timeZoneName; - } -} diff --git a/dart/lib/src/event_processor/enricher/io_platform_memory.dart b/dart/lib/src/event_processor/enricher/io_platform_memory.dart deleted file mode 100644 index 89a0a31ee7..0000000000 --- a/dart/lib/src/event_processor/enricher/io_platform_memory.dart +++ /dev/null @@ -1,143 +0,0 @@ -import 'dart:io'; - -import '../../protocol.dart'; -import '../../sentry_options.dart'; - -// Get total & free platform memory (in bytes) for linux and windows operating systems. -// Source: https://github.com/onepub-dev/system_info/blob/8a9bf6b8eb7c86a09b3c3df4bf6d7fa5a6b50732/lib/src/platform/memory.dart -class PlatformMemory { - PlatformMemory(this.options) { - if (options.platform.isWindows) { - // Check for WMIC (deprecated in newer Windows versions) - // https://techcommunity.microsoft.com/blog/windows-itpro-blog/wmi-command-line-wmic-utility-deprecation-next-steps/4039242 - useWindowsWmci = - File('C:\\Windows\\System32\\wbem\\wmic.exe').existsSync(); - if (!useWindowsWmci) { - useWindowsPowerShell = File( - 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe') - .existsSync(); - } else { - useWindowsPowerShell = false; - } - } else { - useWindowsWmci = false; - useWindowsPowerShell = false; - } - } - - final SentryOptions options; - late final bool useWindowsWmci; - late final bool useWindowsPowerShell; - - Future getTotalPhysicalMemory() async { - if (options.platform.isLinux) { - return _getLinuxMemInfoValue('MemTotal'); - } else if (options.platform.isWindows) { - if (useWindowsWmci) { - return _getWindowsWmicValue('ComputerSystem', 'TotalPhysicalMemory'); - } else if (useWindowsPowerShell) { - return _getWindowsPowershellTotalMemoryValue(); - } else { - return null; - } - } else { - return null; - } - } - - Future _getWindowsWmicValue(String section, String key) async { - final os = await _wmicGetValueAsMap(section, [key]); - final totalPhysicalMemoryValue = os?[key]; - if (totalPhysicalMemoryValue == null) { - return null; - } - final size = int.tryParse(totalPhysicalMemoryValue); - if (size == null) { - return null; - } - return size; - } - - Future _getLinuxMemInfoValue(String key) async { - final result = await _exec('cat', ['/proc/meminfo']); - final meminfoList = - result?.trim().replaceAll('\r\n', '\n').split('\n') ?? []; - - final meminfoMap = _listToMap(meminfoList, ':'); - final memsizeResults = meminfoMap[key]?.split(' ') ?? []; - - if (memsizeResults.isEmpty) { - return null; - } - final memsizeResult = memsizeResults.first; - - final memsize = int.tryParse(memsizeResult); - if (memsize == null) { - return null; - } - return memsize; - } - - Future _exec(String executable, List arguments, - {bool runInShell = false}) async { - try { - final result = - await Process.run(executable, arguments, runInShell: runInShell); - if (result.exitCode == 0) { - return result.stdout.toString(); - } - } catch (e) { - options.log(SentryLevel.warning, "Failed to run process: $e"); - if (options.automatedTestMode) { - rethrow; - } - } - return null; - } - - Future?> _wmicGetValueAsMap( - String section, List fields) async { - final arguments = [section]; - arguments - ..add('get') - ..addAll(fields.join(', ').split(' ')) - ..add('/VALUE'); - - final result = await _exec('wmic', arguments); - final list = result?.trim().replaceAll('\r\n', '\n').split('\n') ?? []; - - return _listToMap(list, '='); - } - - Map _listToMap(List list, String separator) { - final map = {}; - for (final string in list) { - final index = string.indexOf(separator); - if (index != -1) { - final key = string.substring(0, index).trim(); - final value = string.substring(index + 1).trim(); - map[key] = value; - } - } - return map; - } - - Future _getWindowsPowershellTotalMemoryValue() async { - final command = - 'Get-CimInstance Win32_ComputerSystem | Select-Object -ExpandProperty TotalPhysicalMemory'; - - final result = await _exec('powershell.exe', - ['-NoProfile', '-NonInteractive', '-Command', command]); - if (result == null) { - return null; - } - - final value = result.trim(); - final size = int.tryParse(value); - if (size == null) { - return null; - } - - return size; - } -} diff --git a/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart b/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart deleted file mode 100644 index e1a73612d2..0000000000 --- a/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'package:web/web.dart' as web show window, Window, Navigator; - -import '../../../sentry.dart'; -import 'enricher_event_processor.dart'; -import 'flutter_runtime.dart'; - -EnricherEventProcessor enricherEventProcessor(SentryOptions options) { - return WebEnricherEventProcessor( - web.window, - options, - ); -} - -class WebEnricherEventProcessor implements EnricherEventProcessor { - WebEnricherEventProcessor( - this._window, - this._options, - ); - - final web.Window _window; - - final SentryOptions _options; - - @override - SentryEvent? apply(SentryEvent event, Hint hint) { - // Web has no native integration, so no need to check for it - event.contexts - ..device = _getDevice(event.contexts.device) - ..culture = _getSentryCulture(event.contexts.culture) - ..runtimes = _getRuntimes(event.contexts.runtimes); - - event.contexts['dart_context'] = _getDartContext(); - - return event - ..request = _getRequest(event.request) - ..transaction = event.transaction ?? _window.location.pathname; - } - - // As seen in - // https://github.com/getsentry/sentry-javascript/blob/a6f8dc26a4c7ae2146ae64995a2018c8578896a6/packages/browser/src/integrations/useragent.ts - SentryRequest _getRequest(SentryRequest? request) { - final requestHeader = request?.headers; - final header = requestHeader == null - ? {} - : Map.from(requestHeader); - - header.putIfAbsent('User-Agent', () => _window.navigator.userAgent); - - final url = request?.url ?? _window.location.toString(); - request ??= SentryRequest(url: url); - return request - ..headers = header - ..sanitize(); - } - - SentryDevice _getDevice(SentryDevice? device) { - device ??= SentryDevice(); - return device - ..online = device.online ?? _window.navigator.onLine - ..memorySize = device.memorySize ?? _getMemorySize() - ..orientation = device.orientation ?? _getScreenOrientation() - ..screenHeightPixels = - device.screenHeightPixels ?? _window.screen.availHeight - ..screenWidthPixels = - device.screenWidthPixels ?? _window.screen.availWidth - ..screenDensity = - device.screenDensity ?? _window.devicePixelRatio.toDouble(); - } - - int? _getMemorySize() { - // https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory - // ignore: invalid_null_aware_operator - final size = _window.navigator.deviceMemory?.toDouble(); - final memoryByteSize = size != null ? size * 1024 * 1024 * 1024 : null; - return memoryByteSize?.toInt(); - } - - SentryOrientation? _getScreenOrientation() { - // https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation - final screenOrientation = _window.screen.orientation; - if (screenOrientation.type.startsWith('portrait')) { - return SentryOrientation.portrait; - } - if (screenOrientation.type.startsWith('landscape')) { - return SentryOrientation.landscape; - } - return null; - } - - Map _getDartContext() { - return { - 'compile_mode': _options.runtimeChecker.compileMode, - }; - } - - SentryCulture _getSentryCulture(SentryCulture? culture) { - culture ??= SentryCulture(); - return culture..timezone = culture.timezone ?? DateTime.now().timeZoneName; - } - - List _getRuntimes(List? runtimes) { - final flRuntime = flutterRuntime; - final dartFlRuntime = dartFlutterRuntime; - - if (runtimes == null) { - return [ - if (flRuntime != null) flRuntime, - if (dartFlRuntime != null) dartFlRuntime, - ]; - } - return [ - ...runtimes, - if (flRuntime != null) flRuntime, - if (dartFlRuntime != null) dartFlRuntime, - ]; - } -} - -extension on web.Navigator { - // ignore: unused_element - external double? get deviceMemory; -} diff --git a/dart/lib/src/event_processor/exception/exception_event_processor.dart b/dart/lib/src/event_processor/exception/exception_event_processor.dart deleted file mode 100644 index 6e65c72391..0000000000 --- a/dart/lib/src/event_processor/exception/exception_event_processor.dart +++ /dev/null @@ -1,9 +0,0 @@ -import '../../event_processor.dart'; -import '../../sentry_options.dart'; -import 'io_exception_event_processor.dart' - if (dart.library.js_interop) 'web_exception_event_processor.dart'; - -abstract class ExceptionEventProcessor implements EventProcessor { - factory ExceptionEventProcessor(SentryOptions options) => - exceptionEventProcessor(options); -} diff --git a/dart/lib/src/event_processor/exception/exception_group_event_processor.dart b/dart/lib/src/event_processor/exception/exception_group_event_processor.dart deleted file mode 100644 index f0b22d66d6..0000000000 --- a/dart/lib/src/event_processor/exception/exception_group_event_processor.dart +++ /dev/null @@ -1,68 +0,0 @@ -import '../../event_processor.dart'; -import '../../protocol.dart'; -import '../../hint.dart'; -import '../../sentry_options.dart'; - -/// Group exceptions into a flat list with references to hierarchy. -class ExceptionGroupEventProcessor implements EventProcessor { - final SentryOptions _options; - - ExceptionGroupEventProcessor(this._options); - - @override - SentryEvent? apply(SentryEvent event, Hint hint) { - final sentryExceptions = event.exceptions ?? []; - if (sentryExceptions.isEmpty) { - return event; - } - final firstException = sentryExceptions.first; - - if (sentryExceptions.length > 1 || firstException.exceptions == null) { - // If already a list or no child exceptions, no grouping possible/needed. - return event; - } else { - if (_options.groupExceptions) { - event.exceptions = firstException - .flatten(groupExceptions: true) - .reversed - .toList(growable: false); - } else { - event.exceptions = firstException.flatten(groupExceptions: false); - } - return event; - } - } -} - -extension _SentryExceptionFlatten on SentryException { - List flatten( - {int? parentId, int id = 0, required bool groupExceptions}) { - final exceptions = this.exceptions ?? []; - - if (groupExceptions) { - final newMechanism = mechanism ?? Mechanism(type: "generic"); - newMechanism - ..type = id > 0 ? "chained" : newMechanism.type - ..parentId = parentId - ..exceptionId = id - ..isExceptionGroup = exceptions.isNotEmpty ? true : null; - - mechanism = newMechanism; - } - - var all = []; - all.add(this); - - if (exceptions.isNotEmpty) { - final parentId = id; - for (var exception in exceptions) { - id++; - final flattenedExceptions = exception.flatten( - parentId: parentId, id: id, groupExceptions: groupExceptions); - id = flattenedExceptions.lastOrNull?.mechanism?.exceptionId ?? id; - all.addAll(flattenedExceptions); - } - } - return all.toList(growable: false); - } -} diff --git a/dart/lib/src/event_processor/exception/io_exception_event_processor.dart b/dart/lib/src/event_processor/exception/io_exception_event_processor.dart deleted file mode 100644 index 1b698d5c7a..0000000000 --- a/dart/lib/src/event_processor/exception/io_exception_event_processor.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'dart:io'; - -import '../../hint.dart'; -import '../../protocol.dart'; -import '../../sentry_options.dart'; -import 'exception_event_processor.dart'; - -ExceptionEventProcessor exceptionEventProcessor(SentryOptions options) => - IoExceptionEventProcessor(options); - -class IoExceptionEventProcessor implements ExceptionEventProcessor { - IoExceptionEventProcessor(this._options); - - final SentryOptions _options; - - @override - SentryEvent? apply(SentryEvent event, Hint hint) { - final throwable = event.throwable; - if (throwable is HttpException) { - return _applyHttpException(throwable, event); - } - if (throwable is SocketException) { - return _applySocketException(throwable, event); - } - if (throwable is FileSystemException) { - return _applyFileSystemException(throwable, event); - } - - return event; - } - - // https://api.dart.dev/stable/dart-io/HttpException-class.html - SentryEvent _applyHttpException(HttpException exception, SentryEvent event) { - final uri = exception.uri; - if (uri == null) { - return event; - } - return event..request = event.request ?? SentryRequest.fromUri(uri: uri); - } - - // https://api.dart.dev/stable/dart-io/SocketException-class.html - SentryEvent _applySocketException( - SocketException exception, - SentryEvent event, - ) { - final osError = exception.osError; - SentryException? osException; - List? exceptions = event.exceptions; - if (osError != null) { - // OSError is the underlying error - // https://api.dart.dev/stable/dart-io/SocketException/osError.html - // https://api.dart.dev/stable/dart-io/OSError-class.html - osException = _sentryExceptionFromOsError(osError); - final exception = event.exceptions?.firstOrNull; - if (exception != null) { - exception.addException(osException); - } else { - exceptions = [osException]; - } - } else { - exceptions = event.exceptions; - } - - final address = exception.address; - if (address == null) { - event.exceptions = exceptions; - return event; - } - SentryRequest? request; - try { - var uri = Uri.parse(address.host); - request = SentryRequest.fromUri(uri: uri); - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'Could not parse ${address.host} to Uri', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } - - return event - ..request = event.request ?? request - ..exceptions = exceptions; - } - - // https://api.dart.dev/stable/dart-io/FileSystemException-class.html - SentryEvent _applyFileSystemException( - FileSystemException exception, - SentryEvent event, - ) { - final osError = exception.osError; - - if (osError != null) { - // OSError is the underlying error - // https://api.dart.dev/stable/dart-io/SocketException/osError.html - // https://api.dart.dev/stable/dart-io/OSError-class.html - final osException = _sentryExceptionFromOsError(osError); - final exception = event.exceptions?.firstOrNull; - if (exception != null) { - exception.addException(osException); - } else { - event.exceptions = [osException]; - } - } - return event; - } -} - -SentryException _sentryExceptionFromOsError(OSError osError) { - return SentryException( - type: osError.runtimeType.toString(), - value: osError.toString(), - // osError.errorCode is likely a posix signal - // https://develop.sentry.dev/sdk/event-payloads/types/#mechanismmeta - mechanism: Mechanism( - type: 'OSError', - meta: { - 'errno': {'number': osError.errorCode}, - }, - source: 'osError', - ), - ); -} diff --git a/dart/lib/src/event_processor/exception/web_exception_event_processor.dart b/dart/lib/src/event_processor/exception/web_exception_event_processor.dart deleted file mode 100644 index 6ce3be0fe0..0000000000 --- a/dart/lib/src/event_processor/exception/web_exception_event_processor.dart +++ /dev/null @@ -1,12 +0,0 @@ -import '../../hint.dart'; -import '../../protocol.dart'; -import '../../sentry_options.dart'; -import 'exception_event_processor.dart'; - -ExceptionEventProcessor exceptionEventProcessor(SentryOptions _) => - WebExcptionEventProcessor(); - -class WebExcptionEventProcessor implements ExceptionEventProcessor { - @override - SentryEvent apply(SentryEvent event, Hint hint) => event; -} diff --git a/dart/lib/src/event_processor/run_event_processors.dart b/dart/lib/src/event_processor/run_event_processors.dart deleted file mode 100644 index 9af583a12a..0000000000 --- a/dart/lib/src/event_processor/run_event_processors.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../client_reports/discard_reason.dart'; -import '../event_processor.dart'; -import '../hint.dart'; -import '../protocol/sentry_event.dart'; -import '../protocol/sentry_level.dart'; -import '../protocol/sentry_transaction.dart'; -import '../sentry_options.dart'; -import '../transport/data_category.dart'; - -@internal -Future runEventProcessors( - SentryEvent event, - Hint hint, - List eventProcessors, - SentryOptions options, -) async { - int spanCountBeforeEventProcessors = - event is SentryTransaction ? event.spans.length : 0; - - SentryEvent? processedEvent = event; - for (final processor in eventProcessors) { - try { - final e = processor.apply(processedEvent!, hint); - processedEvent = e is Future ? await e : e; - } catch (exception, stackTrace) { - options.log( - SentryLevel.error, - 'An exception occurred while processing event by a processor', - exception: exception, - stackTrace: stackTrace, - ); - if (options.automatedTestMode) { - rethrow; - } - } - - final discardReason = DiscardReason.eventProcessor; - if (processedEvent == null) { - options.recorder.recordLostEvent(discardReason, _getCategory(event)); - if (event is SentryTransaction) { - // We dropped the whole transaction, the dropped count includes all child spans + 1 root span - options.recorder.recordLostEvent( - discardReason, - DataCategory.span, - count: spanCountBeforeEventProcessors + 1, - ); - } - options.log(SentryLevel.debug, 'Event was dropped by a processor'); - break; - } else if (event is SentryTransaction && - processedEvent is SentryTransaction) { - // If event processor removed only some spans we still report them as dropped - final spanCountAfterEventProcessors = processedEvent.spans.length; - final droppedSpanCount = - spanCountBeforeEventProcessors - spanCountAfterEventProcessors; - if (droppedSpanCount > 0) { - options.recorder.recordLostEvent( - discardReason, - DataCategory.span, - count: droppedSpanCount, - ); - } - } - } - - return processedEvent; -} - -DataCategory _getCategory(SentryEvent event) { - if (event is SentryTransaction) { - return DataCategory.transaction; - } else if (event.type == 'feedback') { - return DataCategory.feedback; - } else { - return DataCategory.error; - } -} diff --git a/dart/lib/src/exception_cause.dart b/dart/lib/src/exception_cause.dart deleted file mode 100644 index 60bf12e31e..0000000000 --- a/dart/lib/src/exception_cause.dart +++ /dev/null @@ -1,8 +0,0 @@ -/// Holds inner exception and stackTrace combinations contained in other exceptions -class ExceptionCause { - ExceptionCause(this.exception, this.stackTrace, {this.source}); - - dynamic exception; - dynamic stackTrace; - String? source; -} diff --git a/dart/lib/src/exception_cause_extractor.dart b/dart/lib/src/exception_cause_extractor.dart deleted file mode 100644 index 2913ac18bc..0000000000 --- a/dart/lib/src/exception_cause_extractor.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'protocol.dart'; -import 'exception_cause.dart'; -import 'sentry_options.dart'; - -/// Extend this abstract class and return inner [ExceptionCause] of your -/// exceptions. -/// -/// Implementing an extractor and providing it through -/// [SentryOptions.addExceptionCauseExtractor] will enable the framework to -/// extract the inner exceptions and add them as [SentryException] to -/// [SentryEvent.exceptions]. -/// -/// For an example on how to use the API refer to dio/DioErrorExtractor or the -/// code below: -/// -/// ```dart -/// class ExceptionWithInner { -/// ExceptionWithInner(this.innerException, this.innerStackTrace); -/// Object innerException; -/// StackTrace innerStackTrace; -/// } -/// -/// class ExceptionWithInnerExtractor extends ExceptionCauseExtractor { -/// @override -/// ExceptionCause? cause(ExceptionWithInner error) { -/// return ExceptionCause(error.innerException, error.innerStackTrace); -/// } -/// } -/// -/// options.addExceptionCauseExtractor(ExceptionWithInnerExtractor()); -/// ``` -abstract class ExceptionCauseExtractor { - ExceptionCause? cause(T error); - Type get exceptionType => T; -} diff --git a/dart/lib/src/exception_stacktrace_extractor.dart b/dart/lib/src/exception_stacktrace_extractor.dart deleted file mode 100644 index bf7b785bb6..0000000000 --- a/dart/lib/src/exception_stacktrace_extractor.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'protocol.dart'; -import 'sentry_options.dart'; - -/// Sentry handles [Error.stackTrace] by default. For other cases -/// extend this abstract class and return a custom [StackTrace] of your -/// exceptions. -/// -/// Implementing an extractor and providing it through -/// [SentryOptions.addExceptionStackTraceExtractor] will enable the framework to -/// extract the inner stacktrace and add it to [SentryException] when no other -/// stacktrace was provided while capturing the event. -/// -/// For an example on how to use the API refer to dio/DioStackTraceExtractor or the -/// code below: -/// -/// ```dart -/// class ExceptionWithInner { -/// ExceptionWithInner(this.innerException, this.innerStackTrace); -/// Object innerException; -/// dynamic innerStackTrace; -/// } -/// -/// class ExceptionWithInnerStackTraceExtractor extends ExceptionStackTraceExtractor { -/// @override -/// dynamic cause(ExceptionWithInner error) { -/// return error.innerStackTrace; -/// } -/// } -/// -/// options.addExceptionStackTraceExtractor(ExceptionWithInnerStackTraceExtractor()); -/// ``` -abstract class ExceptionStackTraceExtractor { - dynamic stackTrace(T error); - Type get exceptionType => T; -} diff --git a/dart/lib/src/exception_type_identifier.dart b/dart/lib/src/exception_type_identifier.dart deleted file mode 100644 index 7dae9db841..0000000000 --- a/dart/lib/src/exception_type_identifier.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:meta/meta.dart'; - -/// An abstract class for identifying the type of Dart errors and exceptions. -/// -/// It's used in scenarios where error types need to be determined in obfuscated builds -/// as [runtimeType] is not reliable in such cases. -/// -/// Implement this class to create custom error type identifiers for errors or exceptions. -/// that we do not support out of the box. -/// -/// Example: -/// ```dart -/// class MyExceptionTypeIdentifier implements ExceptionTypeIdentifier { -/// @override -/// String? identifyType(dynamic throwable) { -/// if (throwable is MyCustomError) return 'MyCustomError'; -/// return null; -/// } -/// } -/// ``` -abstract class ExceptionTypeIdentifier { - String? identifyType(dynamic throwable); -} - -extension CacheableExceptionIdentifier on ExceptionTypeIdentifier { - ExceptionTypeIdentifier withCache() => CachingExceptionTypeIdentifier(this); -} - -@visibleForTesting -class CachingExceptionTypeIdentifier implements ExceptionTypeIdentifier { - @visibleForTesting - ExceptionTypeIdentifier get identifier => _identifier; - final ExceptionTypeIdentifier _identifier; - - final Map _knownExceptionTypes = {}; - - CachingExceptionTypeIdentifier(this._identifier); - - @override - String? identifyType(dynamic throwable) { - final runtimeType = throwable.runtimeType; - if (_knownExceptionTypes.containsKey(runtimeType)) { - return _knownExceptionTypes[runtimeType]; - } - - final identifiedType = _identifier.identifyType(throwable); - - if (identifiedType != null) { - _knownExceptionTypes[runtimeType] = identifiedType; - } - - return identifiedType; - } -} diff --git a/dart/lib/src/feature_flags_integration.dart b/dart/lib/src/feature_flags_integration.dart deleted file mode 100644 index 4a19d16092..0000000000 --- a/dart/lib/src/feature_flags_integration.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'dart:async'; - -import 'hub.dart'; -import 'integration.dart'; -import 'sentry_options.dart'; -import 'protocol/sentry_feature_flags.dart'; -import 'protocol/sentry_feature_flag.dart'; - -/// Integration which handles adding feature flags to the scope. -class FeatureFlagsIntegration extends Integration { - Hub? _hub; - - @override - void call(Hub hub, SentryOptions options) { - _hub = hub; - options.sdk.addIntegration('FeatureFlagsIntegration'); - } - - FutureOr addFeatureFlag(String flag, bool result) async { - final flags = - _hub?.scope.contexts[SentryFeatureFlags.type] as SentryFeatureFlags? ?? - SentryFeatureFlags(values: []); - final values = flags.values; - - if (values.length >= 100) { - values.removeAt(0); - } - - final index = values.indexWhere((element) => element.flag == flag); - if (index != -1) { - values[index] = SentryFeatureFlag(flag: flag, result: result); - } else { - values.add(SentryFeatureFlag(flag: flag, result: result)); - } - - flags.values = values; - - await _hub?.scope.setContexts(SentryFeatureFlags.type, flags); - } - - @override - FutureOr close() { - _hub = null; - } -} diff --git a/dart/lib/src/hint.dart b/dart/lib/src/hint.dart deleted file mode 100644 index 95a22fd0ad..0000000000 --- a/dart/lib/src/hint.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'protocol/sentry_response.dart'; -import 'sentry_attachment/sentry_attachment.dart'; - -import 'package:meta/meta.dart'; - -/// Hints are used in [BeforeSendCallback], [BeforeBreadcrumbCallback] and -/// event processors. -/// -/// Event and breadcrumb hints are objects containing various information used -/// to put together an event or a breadcrumb. Typically hints hold the original -/// exception so that additional data can be extracted or grouping can be -/// affected. -/// -/// Example: -/// -/// ```dart -/// options.beforeSend = (event, hint) { -/// final syntheticException = hint.get(TypeCheckHint.syntheticException); -/// if (syntheticException is FlutterErrorDetails) { -/// // Do something with hint data -/// } -/// return event; -/// }; -/// } -/// ``` -/// -/// The [Hint] can also be used to add attachments to events. -/// -/// Example: -/// -/// ```dart -/// import 'dart:convert'; -/// -/// options.beforeSend = (event, hint) { -/// final text = 'This event should not be sent happen in prod. Investigate.'; -/// final textAttachment = SentryAttachment.fromIntList( -/// utf8.encode(text), -/// 'event_info.txt', -/// contentType: 'text/plain', -/// ); -/// hint.attachments.add(textAttachment); -/// return event; -/// }; -/// ``` -class Hint { - @internal - static const maxResponseBodySize = 157286; - - final Map _internalStorage = {}; - - final List attachments = []; - - SentryAttachment? screenshot; - - SentryAttachment? viewHierarchy; - - SentryResponse? response; - - Hint(); - - factory Hint.withAttachment(SentryAttachment attachment) { - final hint = Hint(); - hint.attachments.add(attachment); - return hint; - } - - factory Hint.withAttachments(List attachments) { - final hint = Hint(); - hint.attachments.addAll(attachments); - return hint; - } - - factory Hint.withMap(Map map) { - final hint = Hint(); - hint.addAll(map); - return hint; - } - - factory Hint.withScreenshot(SentryAttachment screenshot) { - final hint = Hint(); - hint.screenshot = screenshot; - return hint; - } - - factory Hint.withViewHierarchy(SentryAttachment viewHierarchy) { - final hint = Hint(); - hint.viewHierarchy = viewHierarchy; - return hint; - } - - factory Hint.withResponse(SentryResponse response) { - final hint = Hint(); - hint.response = response; - return hint; - } - - // Key/Value Storage - - void addAll(Map keysAndValues) { - final withoutNullValues = - keysAndValues.map((key, value) => MapEntry(key, value ?? "null")); - _internalStorage.addAll(withoutNullValues); - } - - void set(String key, dynamic value) { - _internalStorage[key] = value ?? "null"; - } - - dynamic get(String key) { - return _internalStorage[key]; - } - - void remove(String key) { - _internalStorage.remove(key); - } - - void clear() { - _internalStorage.clear(); - } -} diff --git a/dart/lib/src/http_client/breadcrumb_client.dart b/dart/lib/src/http_client/breadcrumb_client.dart deleted file mode 100644 index 7f9a1b50d9..0000000000 --- a/dart/lib/src/http_client/breadcrumb_client.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'package:http/http.dart'; -import '../protocol.dart'; -import '../hub.dart'; -import '../hub_adapter.dart'; -import '../utils/breadcrumb_log_level.dart'; -import '../utils/url_details.dart'; -import '../utils/http_sanitizer.dart'; - -/// A [http](https://pub.dev/packages/http)-package compatible HTTP client -/// which records requests as breadcrumbs. -/// -/// Remarks: -/// If this client is used as a wrapper, a call to close also closes the -/// given client. -/// -/// The `BreadcrumbClient` can be used as a standalone client like this: -/// ```dart -/// import 'package:sentry/sentry.dart'; -/// -/// var client = BreadcrumbClient(); -/// try { -/// var uriResponse = await client.post('https://example.com/whatsit/create', -/// body: {'name': 'doodle', 'color': 'blue'}); -/// print(await client.get(uriResponse.bodyFields['uri'])); -/// } finally { -/// client.close(); -/// } -/// ``` -/// -/// The `BreadcrumbClient` can also be used as a wrapper for your own -/// HTTP [Client](https://pub.dev/documentation/http/latest/http/Client-class.html): -/// ```dart -/// import 'package:sentry/sentry.dart'; -/// import 'package:http/http.dart' as http; -/// -/// final myClient = http.Client(); -/// -/// var client = BreadcrumbClient(client: myClient); -/// try { -/// var uriResponse = await client.post('https://example.com/whatsit/create', -/// body: {'name': 'doodle', 'color': 'blue'}); -/// print(await client.get(uriResponse.bodyFields['uri'])); -/// } finally { -/// client.close(); -/// } -/// ``` -class BreadcrumbClient extends BaseClient { - BreadcrumbClient({Client? client, Hub? hub}) - : _hub = hub ?? HubAdapter(), - _client = client ?? Client(); - - final Client _client; - final Hub _hub; - - @override - Future send(BaseRequest request) async { - // See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/ - - var requestHadException = false; - int? statusCode; - String? reason; - int? responseBodySize; - - final stopwatch = Stopwatch(); - stopwatch.start(); - - try { - final response = await _client.send(request); - - statusCode = response.statusCode; - reason = response.reasonPhrase; - responseBodySize = response.contentLength; - - return response; - } catch (_) { - requestHadException = true; - rethrow; - } finally { - stopwatch.stop(); - - final urlDetails = - HttpSanitizer.sanitizeUrl(request.url.toString()) ?? UrlDetails(); - - SentryLevel? level; - if (requestHadException) { - level = SentryLevel.error; - } else if (statusCode != null) { - level = getBreadcrumbLogLevelFromHttpStatusCode(statusCode); - } - - var breadcrumb = Breadcrumb.http( - level: level, - url: Uri.parse(urlDetails.urlOrFallback), - method: request.method, - statusCode: statusCode, - reason: reason, - requestDuration: stopwatch.elapsed, - requestBodySize: request.contentLength, - responseBodySize: responseBodySize, - httpQuery: urlDetails.query, - httpFragment: urlDetails.fragment, - ); - - await _hub.addBreadcrumb(breadcrumb); - } - } - - @override - void close() => _client.close(); -} diff --git a/dart/lib/src/http_client/client_provider.dart b/dart/lib/src/http_client/client_provider.dart deleted file mode 100644 index 201a559601..0000000000 --- a/dart/lib/src/http_client/client_provider.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:http/http.dart'; - -import '../sentry_options.dart'; - -@internal -ClientProvider getClientProvider() { - return ClientProvider(); -} - -@internal -class ClientProvider { - Client getClient(SentryOptions options) { - return Client(); - } -} diff --git a/dart/lib/src/http_client/failed_request_client.dart b/dart/lib/src/http_client/failed_request_client.dart deleted file mode 100644 index 2e494e02bb..0000000000 --- a/dart/lib/src/http_client/failed_request_client.dart +++ /dev/null @@ -1,254 +0,0 @@ -import 'package:http/http.dart'; - -import '../hint.dart'; -import '../hub.dart'; -import '../hub_adapter.dart'; -import '../protocol.dart'; -import '../throwable_mechanism.dart'; -import '../type_check_hint.dart'; -import '../utils/tracing_utils.dart'; -import 'sentry_http_client.dart'; -import 'sentry_http_client_error.dart'; - -/// A [http](https://pub.dev/packages/http)-package compatible HTTP client -/// which records events for failed requests. -/// -/// Configured with default values, this captures requests which throw an -/// exception. -/// This can be for example for the following reasons: -/// - In an browser environment this can be requests which fail because of CORS. -/// - In an mobile or desktop application this can be requests which failed -/// because the connection was interrupted. -/// -/// Additionally you can configure specific HTTP response codes to be considered -/// as a failed request. In the following example, the status codes 404 and 500 -/// are considered a failed request. -/// -/// ```dart -/// import 'package:sentry/sentry.dart'; -/// -/// var client = FailedRequestClient( -/// failedRequestStatusCodes: [SentryStatusCode.range(400, 404), SentryStatusCode(500)] -/// ); -/// ``` -/// -/// Remarks: -/// If this client is used as a wrapper, a call to close also closes the -/// given client. -/// -/// The `FailedRequestClient` can be used as a standalone client like this: -/// ```dart -/// import 'package:sentry/sentry.dart'; -/// -/// var client = FailedRequestClient(); -/// try { -/// var uriResponse = await client.post('https://example.com/whatsit/create', -/// body: {'name': 'doodle', 'color': 'blue'}); -/// print(await client.get(uriResponse.bodyFields['uri'])); -/// } finally { -/// client.close(); -/// } -/// ``` -/// -/// The `FailedRequestClient` can also be used as a wrapper for your own -/// HTTP [Client](https://pub.dev/documentation/http/latest/http/Client-class.html): -/// ```dart -/// import 'package:sentry/sentry.dart'; -/// import 'package:http/http.dart' as http; -/// -/// final myClient = http.Client(); -/// -/// var client = FailedRequestClient(client: myClient); -/// try { -/// var uriResponse = await client.post('https://example.com/whatsit/create', -/// body: {'name': 'doodle', 'color': 'blue'}); -/// print(await client.get(uriResponse.bodyFields['uri'])); -/// } finally { -/// client.close(); -/// } -/// ``` -class FailedRequestClient extends BaseClient { - FailedRequestClient({ - this.failedRequestStatusCodes = - SentryHttpClient.defaultFailedRequestStatusCodes, - this.failedRequestTargets = SentryHttpClient.defaultFailedRequestTargets, - Client? client, - Hub? hub, - bool? captureFailedRequests, - }) : _hub = hub ?? HubAdapter(), - _client = client ?? Client(), - _captureFailedRequests = captureFailedRequests { - if (captureFailedRequests ?? _hub.options.captureFailedRequests) { - _hub.options.sdk.addIntegration('HTTPClientError'); - } - } - - final Client _client; - final Hub _hub; - final bool? _captureFailedRequests; - - /// Describes which HTTP status codes should be considered as a failed - /// requests. - /// - /// Per default no status code is considered a failed request. - final List failedRequestStatusCodes; - - final List failedRequestTargets; - - @override - Future send(BaseRequest request) async { - int? statusCode; - Object? exception; - StackTrace? stackTrace; - StreamedResponse? response; - - final stopwatch = Stopwatch(); - stopwatch.start(); - - try { - response = await _client.send(request); - statusCode = response.statusCode; - return response; - } catch (e, st) { - exception = e; - stackTrace = st; - rethrow; - } finally { - stopwatch.stop(); - await _captureEventIfNeeded( - request, - statusCode, - exception, - stackTrace, - response, - stopwatch.elapsed, - ); - } - } - - Future _captureEventIfNeeded( - BaseRequest request, - int? statusCode, - Object? exception, - StackTrace? stackTrace, - StreamedResponse? response, - Duration duration) async { - if (!(_captureFailedRequests ?? _hub.options.captureFailedRequests)) { - return; - } - - // Only check `failedRequestStatusCodes` & `failedRequestTargets` if no exception was thrown. - if (exception == null) { - if (!failedRequestStatusCodes._containsStatusCode(statusCode)) { - return; - } - if (!containsTargetOrMatchesRegExp( - failedRequestTargets, request.url.toString())) { - return; - } - } - - final reason = 'HTTP Client Error with status code: $statusCode'; - exception ??= SentryHttpClientError(reason); - - await _captureEvent( - exception: exception, - stackTrace: stackTrace, - request: request, - requestDuration: duration, - response: response, - reason: reason, - ); - } - - @override - void close() => _client.close(); - - // See https://develop.sentry.dev/sdk/event-payloads/request/ - Future _captureEvent({ - required Object? exception, - StackTrace? stackTrace, - String? reason, - required Duration requestDuration, - required BaseRequest request, - required StreamedResponse? response, - }) async { - final sentryRequest = SentryRequest.fromUri( - method: request.method, - headers: _hub.options.sendDefaultPii ? request.headers : null, - uri: request.url, - data: _hub.options.sendDefaultPii ? _getDataFromRequest(request) : null, - ); - - final mechanism = Mechanism( - type: 'SentryHttpClient', - description: reason, - ); - - bool? snapshot; - if (exception is SentryHttpClientError) { - snapshot = true; - } - - final throwableMechanism = ThrowableMechanism( - mechanism, - exception, - snapshot: snapshot, - ); - - final event = SentryEvent( - throwable: throwableMechanism, - request: sentryRequest, - timestamp: _hub.options.clock(), - ); - - final hint = Hint.withMap({TypeCheckHint.httpRequest: request}); - - if (response != null) { - event.contexts.response = SentryResponse( - headers: _hub.options.sendDefaultPii ? response.headers : null, - bodySize: response.contentLength, - statusCode: response.statusCode, - ); - hint.set(TypeCheckHint.httpResponse, response); - } - - await _hub.captureEvent( - event, - stackTrace: stackTrace, - hint: hint, - ); - } - - // Types of Request can be found here: - // https://pub.dev/documentation/http/latest/http/http-library.html - Object? _getDataFromRequest(BaseRequest request) { - final contentLength = request.contentLength; - if (contentLength == null) { - return null; - } - if (!_hub.options.maxRequestBodySize.shouldAddBody(contentLength)) { - return null; - } - if (request is MultipartRequest) { - final data = {...request.fields}; - return data; - } - - if (request is Request) { - return request.body; - } - - // There's nothing we can do for a StreamedRequest - return null; - } -} - -extension _ListX on List { - bool _containsStatusCode(int? statusCode) { - if (statusCode == null) { - return false; - } - return any((element) => element.isInRange(statusCode)); - } -} diff --git a/dart/lib/src/http_client/io_client_provider.dart b/dart/lib/src/http_client/io_client_provider.dart deleted file mode 100644 index 6e611cbb97..0000000000 --- a/dart/lib/src/http_client/io_client_provider.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'dart:io'; - -import 'package:http/http.dart'; -import 'package:http/io_client.dart'; -import 'package:meta/meta.dart'; - -import '../protocol.dart'; -import '../protocol/sentry_proxy.dart'; -import '../sentry_options.dart'; -import 'client_provider.dart'; - -@internal -ClientProvider getClientProvider() { - return IoClientProvider( - () { - return HttpClient(); - }, - (user, pass) { - return HttpClientBasicCredentials(user, pass); - }, - ); -} - -@internal -class IoClientProvider implements ClientProvider { - final HttpClient Function() _httpClient; - final HttpClientCredentials Function(String, String) _httpClientCredentials; - - IoClientProvider(this._httpClient, this._httpClientCredentials); - - @override - Client getClient(SentryOptions options) { - final proxy = options.proxy; - if (proxy == null) { - return Client(); - } - final pac = proxy.toPacString(); - if (proxy.type == SentryProxyType.socks) { - options.log( - SentryLevel.warning, - "Setting proxy '$pac' is not supported.", - ); - return Client(); - } - options.log( - SentryLevel.info, - "Setting proxy '$pac'", - ); - final httpClient = _httpClient(); - httpClient.findProxy = (url) => pac; - - final host = proxy.host; - final port = proxy.port; - final user = proxy.user; - final pass = proxy.pass; - - if (host != null && port != null && user != null && pass != null) { - httpClient.addProxyCredentials( - host, - port, - '', - _httpClientCredentials(user, pass), - ); - } - return IOClient(httpClient); - } -} diff --git a/dart/lib/src/http_client/sentry_http_client.dart b/dart/lib/src/http_client/sentry_http_client.dart deleted file mode 100644 index b6933a07a1..0000000000 --- a/dart/lib/src/http_client/sentry_http_client.dart +++ /dev/null @@ -1,160 +0,0 @@ -import 'package:http/http.dart'; - -import '../hub.dart'; -import '../hub_adapter.dart'; -import 'breadcrumb_client.dart'; -import 'failed_request_client.dart'; -import 'tracing_client.dart'; - -/// A [http](https://pub.dev/packages/http)-package compatible HTTP client. -/// -/// Additionally you can configure specific HTTP response codes to be considered -/// as a failed request. This is off by default. Enable it by using it like -/// shown in the following example: -/// The status codes 400 to 404 and 500 are considered a failed request. -/// -/// ```dart -/// import 'package:sentry/sentry.dart'; -/// -/// var client = SentryHttpClient( -/// failedRequestStatusCodes: [ -/// SentryStatusCode.range(400, 404), -/// SentryStatusCode(500), -/// ], -/// ); -/// ``` -/// -/// If empty request status codes are provided, all failure requests will be -/// captured. Per default, codes in the range 500-599 are recorded. -/// -/// If you provide failed request targets, the SDK will only capture HTTP -/// Client errors if the HTTP Request URL is a match for any of the provided -/// targets. -/// -/// ```dart -/// var client = SentryHttpClient( -/// failedRequestTargets: ['my-api.com'], -/// ); -/// ``` -/// -/// Remarks: If this client is used as a wrapper, a call to close also closes -/// the given client. -/// -/// The `SentryHttpClient` can be used as a standalone client like this: -/// ```dart -/// import 'package:sentry/sentry.dart'; -/// -/// var client = SentryHttpClient(); -/// try { -/// var uriResponse = await client.post('https://example.com/whatsit/create', -/// body: {'name': 'doodle', 'color': 'blue'}); -/// print(await client.get(uriResponse.bodyFields['uri'])); -/// } finally { -/// client.close(); -/// } -/// ``` -/// -/// The `SentryHttpClient` can also be used as a wrapper for your own HTTP -/// [Client](https://pub.dev/documentation/http/latest/http/Client-class.html): -/// ```dart -/// import 'package:sentry/sentry.dart'; -/// import 'package:http/http.dart' as http; -/// -/// final myClient = http.Client(); -/// -/// var client = SentryHttpClient(client: myClient); -/// try { -/// var uriResponse = await client.post('https://example.com/whatsit/create', -/// body: {'name': 'doodle', 'color': 'blue'}); -/// print(await client.get(uriResponse.bodyFields['uri'])); -/// } finally { -/// client.close(); -/// } -/// -/// Remarks: -/// HTTP traffic can contain PII (personal identifiable information). -/// Read more on data scrubbing [here](https://docs.sentry.io/product/data-management-settings/advanced-datascrubbing/). -/// -/// The constructor parameter `captureFailedRequests` will override what you -/// have configured in options. -/// ``` -class SentryHttpClient extends BaseClient { - static const defaultFailedRequestStatusCodes = [ - SentryStatusCode.defaultRange() - ]; - static const defaultFailedRequestTargets = ['.*']; - - SentryHttpClient({ - Client? client, - Hub? hub, - List failedRequestStatusCodes = - defaultFailedRequestStatusCodes, - List failedRequestTargets = defaultFailedRequestTargets, - bool? captureFailedRequests, - }) { - _hub = hub ?? HubAdapter(); - - var innerClient = client ?? Client(); - - innerClient = FailedRequestClient( - failedRequestStatusCodes: failedRequestStatusCodes, - failedRequestTargets: failedRequestTargets, - hub: _hub, - client: innerClient, - captureFailedRequests: captureFailedRequests, - ); - - innerClient = TracingClient(client: innerClient, hub: _hub); - - // The ordering here matters. - // We don't want to include the breadcrumbs for the current request - // when capturing it as a failed request. - // However it still should be added for following events. - if (_hub.options.recordHttpBreadcrumbs) { - innerClient = BreadcrumbClient(client: innerClient, hub: _hub); - } - - _client = innerClient; - } - - late Client _client; - late Hub _hub; - - @override - Future send(BaseRequest request) => _client.send(request); - - // See https://github.com/getsentry/sentry-dart/pull/226#discussion_r536984785 - @override - void close() => _client.close(); -} - -class SentryStatusCode { - static const _defaultMin = 500; - static const _defaultMax = 599; - - const SentryStatusCode.defaultRange() - : _min = _defaultMin, - _max = _defaultMax; - - SentryStatusCode.range(this._min, this._max) - : assert(_min <= _max), - assert(_min > 0 && _max > 0); - - SentryStatusCode(int statusCode) - : _min = statusCode, - _max = statusCode, - assert(statusCode > 0); - - final int _min; - final int _max; - - bool isInRange(int statusCode) => statusCode >= _min && statusCode <= _max; - - @override - String toString() { - if (_min == _max) { - return _min.toString(); - } - return '$_min..$_max'; - } -} diff --git a/dart/lib/src/http_client/sentry_http_client_error.dart b/dart/lib/src/http_client/sentry_http_client_error.dart deleted file mode 100644 index 60e1abdfe3..0000000000 --- a/dart/lib/src/http_client/sentry_http_client_error.dart +++ /dev/null @@ -1,7 +0,0 @@ -class SentryHttpClientError implements Exception { - final String _message; - SentryHttpClientError(this._message); - - @override - String toString() => 'Exception: $_message'; -} diff --git a/dart/lib/src/http_client/tracing_client.dart b/dart/lib/src/http_client/tracing_client.dart deleted file mode 100644 index 8086d13a16..0000000000 --- a/dart/lib/src/http_client/tracing_client.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:http/http.dart'; - -import '../hub.dart'; -import '../hub_adapter.dart'; -import '../protocol.dart'; -import '../sentry_trace_origins.dart'; -import '../tracing.dart'; -import '../utils/http_sanitizer.dart'; -import '../utils/tracing_utils.dart'; - -/// A [http](https://pub.dev/packages/http)-package compatible HTTP client -/// which adds support to Sentry Performance feature. If tracing is disabled -/// generated spans will be no-op. This client also handles adding the -/// Sentry trace headers to the HTTP request header. -/// https://develop.sentry.dev/sdk/performance -class TracingClient extends BaseClient { - static const String integrationName = 'HTTPNetworkTracing'; - - TracingClient({Client? client, Hub? hub}) - : _hub = hub ?? HubAdapter(), - _client = client ?? Client() { - if (_hub.options.isTracingEnabled()) { - _hub.options.sdk.addIntegration(integrationName); - } - } - - final Client _client; - final Hub _hub; - - @override - Future send(BaseRequest request) async { - // see https://develop.sentry.dev/sdk/performance/#header-sentry-trace - final urlDetails = HttpSanitizer.sanitizeUrl(request.url.toString()); - - var description = request.method; - if (urlDetails != null) { - description += ' ${urlDetails.urlOrFallback}'; - } - - final currentSpan = _hub.getSpan(); - var span = currentSpan?.startChild( - 'http.client', - description: description, - ); - - if (span is NoOpSentrySpan) { - span = null; - } - - // Regardless whether tracing is enabled or not, we always want to attach - // Sentry trace headers (tracing without performance). - if (containsTargetOrMatchesRegExp( - _hub.options.tracePropagationTargets, request.url.toString())) { - addTracingHeadersToHttpHeader(request.headers, _hub, span: span); - } - - span?.origin = SentryTraceOrigins.autoHttpHttp; - span?.setData('http.request.method', request.method); - urlDetails?.applyToSpan(span); - - StreamedResponse? response; - try { - response = await _client.send(request); - span?.setData('http.response.status_code', response.statusCode); - span?.setData('http.response_content_length', response.contentLength); - span?.status = SpanStatus.fromHttpStatusCode(response.statusCode); - } catch (exception) { - span?.throwable = exception; - span?.status = SpanStatus.internalError(); - - rethrow; - } finally { - await span?.finish(); - } - return response; - } - - @override - void close() => _client.close(); -} diff --git a/dart/lib/src/hub.dart b/dart/lib/src/hub.dart deleted file mode 100644 index a4533793c7..0000000000 --- a/dart/lib/src/hub.dart +++ /dev/null @@ -1,784 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; -import 'dart:math'; - -import 'package:meta/meta.dart'; - -import '../sentry.dart'; -import 'client_reports/discard_reason.dart'; -import 'profiling.dart'; -import 'sentry_tracer.dart'; -import 'sentry_traces_sampler.dart'; -import 'transport/data_category.dart'; - -/// Configures the scope through the callback. -typedef ScopeCallback = FutureOr Function(Scope); - -/// Called when a transaction is finished. -typedef OnTransactionFinish = FutureOr Function(ISentrySpan transaction); - -/// SDK API contract which combines a client and scope management -class Hub { - static SentryClient _getClient(SentryOptions options) { - return SentryClient(options); - } - - final ListQueue<_StackItem> _stack = ListQueue(); - - // peek can never return null since Stack can be created only with an item and - // pop does not drop the last item. - _StackItem _peek() => _stack.first; - - final SentryOptions _options; - - @internal - SentryOptions get options => _options; - - late SentryTracesSampler _tracesSampler; - - late final _WeakMap _throwableToSpan; - - factory Hub(SentryOptions options) { - _validateOptions(options); - - return Hub._(options); - } - - Hub._(this._options) { - _tracesSampler = SentryTracesSampler(_options); - _stack.add(_StackItem(_getClient(_options), Scope(_options))); - _isEnabled = true; - _throwableToSpan = _WeakMap(_options); - } - - static void _validateOptions(SentryOptions options) { - if (options.dsn == null) { - throw ArgumentError('DSN is required.'); - } - } - - bool _isEnabled = false; - - /// Check if the Hub is enabled/active. - bool get isEnabled => _isEnabled; - - SentryId _lastEventId = SentryId.empty(); - - /// Last event id recorded by the Hub - SentryId get lastEventId => _lastEventId; - - @internal - Scope get scope => _peek().scope; - - /// Captures the event. - Future captureEvent( - SentryEvent event, { - dynamic stackTrace, - Hint? hint, - ScopeCallback? withScope, - }) async { - var sentryId = SentryId.empty(); - - if (!_isEnabled) { - _options.log( - SentryLevel.warning, - "Instance is disabled and this 'captureEvent' call is a no-op.", - ); - } else { - final item = _peek(); - late Scope scope; - final s = _cloneAndRunWithScope(item.scope, withScope); - if (s is Future) { - scope = await s; - } else { - scope = s; - } - - try { - if (_options.isTracingEnabled()) { - event = _assignTraceContext(event); - } - - sentryId = await item.client.captureEvent( - event, - stackTrace: stackTrace, - scope: scope, - hint: hint, - ); - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'Error while capturing event with id: ${event.eventId}', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } finally { - _lastEventId = sentryId; - } - } - return sentryId; - } - - /// Captures the exception - Future captureException( - dynamic throwable, { - dynamic stackTrace, - Hint? hint, - SentryMessage? message, - ScopeCallback? withScope, - }) async { - var sentryId = SentryId.empty(); - - if (!_isEnabled) { - _options.log( - SentryLevel.warning, - "Instance is disabled and this 'captureException' call is a no-op.", - ); - } else if (throwable == null) { - _options.log( - SentryLevel.warning, - 'captureException called with null parameter.', - ); - } else { - final item = _peek(); - late Scope scope; - final s = _cloneAndRunWithScope(item.scope, withScope); - if (s is Future) { - scope = await s; - } else { - scope = s; - } - - try { - var event = SentryEvent( - throwable: throwable, - timestamp: _options.clock(), - message: message, - ); - - if (_options.isTracingEnabled()) { - event = _assignTraceContext(event); - } - - sentryId = await item.client.captureEvent( - event, - stackTrace: stackTrace, - scope: scope, - hint: hint, - ); - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'Error while capturing exception', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } finally { - _lastEventId = sentryId; - } - } - - return sentryId; - } - - /// Captures the message. - Future captureMessage( - String? message, { - SentryLevel? level, - String? template, - List? params, - Hint? hint, - ScopeCallback? withScope, - }) async { - var sentryId = SentryId.empty(); - - if (!_isEnabled) { - _options.log( - SentryLevel.warning, - "Instance is disabled and this 'captureMessage' call is a no-op.", - ); - } else if (message == null) { - _options.log( - SentryLevel.warning, - 'captureMessage called with null parameter.', - ); - } else { - final item = _peek(); - late Scope scope; - final s = _cloneAndRunWithScope(item.scope, withScope); - if (s is Future) { - scope = await s; - } else { - scope = s; - } - - try { - sentryId = await item.client.captureMessage( - message, - level: level, - template: template, - params: params, - scope: scope, - hint: hint, - ); - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'Error while capturing message with id: $message', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } finally { - _lastEventId = sentryId; - } - } - return sentryId; - } - - /// Captures the feedback. - Future captureFeedback( - SentryFeedback feedback, { - Hint? hint, - ScopeCallback? withScope, - }) async { - var sentryId = SentryId.empty(); - - if (!_isEnabled) { - _options.log( - SentryLevel.warning, - "Instance is disabled and this 'captureFeedback' call is a no-op.", - ); - } else { - final item = _peek(); - late Scope scope; - final s = _cloneAndRunWithScope(item.scope, withScope); - if (s is Future) { - scope = await s; - } else { - scope = s; - } - - try { - sentryId = await item.client.captureFeedback( - feedback, - hint: hint, - scope: scope, - ); - } catch (exception, stacktrace) { - _options.log( - SentryLevel.error, - 'Error while capturing feedback', - exception: exception, - stackTrace: stacktrace, - ); - } - } - return sentryId; - } - - FutureOr captureLog(SentryLog log) async { - if (!_isEnabled) { - _options.log( - SentryLevel.warning, - "Instance is disabled and this 'captureFeedback' call is a no-op.", - ); - } else { - final item = _peek(); - late Scope scope; - final s = _cloneAndRunWithScope(item.scope, null); - if (s is Future) { - scope = await s; - } else { - scope = s; - } - - try { - await item.client.captureLog( - log, - scope: scope, - ); - } catch (exception, stacktrace) { - _options.log( - SentryLevel.error, - 'Error while capturing log', - exception: exception, - stackTrace: stacktrace, - ); - } - } - } - - FutureOr _cloneAndRunWithScope( - Scope scope, ScopeCallback? withScope) async { - if (withScope != null) { - try { - scope = scope.clone(); - final s = withScope(scope); - if (s is Future) { - await s; - } - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'Exception in withScope callback.', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } - } - return scope; - } - - /// Adds a breacrumb to the current Scope - Future addBreadcrumb(Breadcrumb crumb, {Hint? hint}) async { - if (!_isEnabled) { - _options.log( - SentryLevel.warning, - "Instance is disabled and this 'addBreadcrumb' call is a no-op.", - ); - } else { - final item = _peek(); - await item.scope.addBreadcrumb(crumb, hint: hint); - } - } - - /// Binds a different client to the hub - void bindClient(SentryClient client) { - if (!_isEnabled) { - _options.log( - SentryLevel.warning, - "Instance is disabled and this 'bindClient' call is a no-op.", - ); - } else { - final item = _peek(); - _options.log(SentryLevel.debug, 'New client bound to scope.'); - item.client = client; - } - } - - /// Clones the Hub - Hub clone() { - if (!_isEnabled) { - _options.log(SentryLevel.warning, 'Disabled Hub cloned.'); - } - final clone = Hub(_options); - for (final item in _stack) { - clone._stack.add(_StackItem(item.client, item.scope.clone())); - } - return clone; - } - - /// Flushes out the queue for up to timeout seconds and disable the Hub. - Future close() async { - if (!_isEnabled) { - _options.log( - SentryLevel.warning, - "Instance is disabled and this 'close' call is a no-op.", - ); - } else { - // close integrations - for (final integration in _options.integrations) { - final close = integration.close(); - if (close is Future) { - await close; - } - } - - final item = _peek(); - - try { - item.client.close(); - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'Error while closing the Hub', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } - - _isEnabled = false; - } - } - - /// Configures the scope through the callback. - FutureOr configureScope(ScopeCallback callback) async { - if (!_isEnabled) { - _options.log( - SentryLevel.warning, - "Instance is disabled and this 'configureScope' call is a no-op.", - ); - } else { - final item = _peek(); - - try { - final result = callback(item.scope); - if (result is Future) { - await result; - } - } catch (err) { - _options.log( - SentryLevel.error, - "Error in the 'configureScope' callback, error: $err", - ); - if (_options.automatedTestMode) { - rethrow; - } - } - } - } - - /// Creates a Transaction and returns the instance. - ISentrySpan startTransaction( - String name, - String operation, { - String? description, - DateTime? startTimestamp, - bool? bindToScope, - bool? waitForChildren, - Duration? autoFinishAfter, - bool? trimEnd, - OnTransactionFinish? onFinish, - Map? customSamplingContext, - }) => - startTransactionWithContext( - SentryTransactionContext( - name, - operation, - description: description, - origin: SentryTraceOrigins.manual, - ), - startTimestamp: startTimestamp, - bindToScope: bindToScope, - waitForChildren: waitForChildren, - autoFinishAfter: autoFinishAfter, - trimEnd: trimEnd, - onFinish: onFinish, - customSamplingContext: customSamplingContext, - ); - - /// Creates a Transaction and returns the instance. - ISentrySpan startTransactionWithContext( - SentryTransactionContext transactionContext, { - Map? customSamplingContext, - DateTime? startTimestamp, - bool? bindToScope, - bool? waitForChildren, - Duration? autoFinishAfter, - bool? trimEnd, - OnTransactionFinish? onFinish, - }) { - if (!_isEnabled) { - _options.log( - SentryLevel.warning, - "Instance is disabled and this 'startTransaction' call is a no-op.", - ); - } else if (_options.isTracingEnabled()) { - final item = _peek(); - - // if transactionContext has no sampling decision yet, run the traces sampler - var samplingDecision = transactionContext.samplingDecision; - final propagationContext = scope.propagationContext; - // Store the generated/used sampleRand on the propagation context so - // that subsequent transactions in the same trace reuse it. - propagationContext.sampleRand ??= Random().nextDouble(); - - if (samplingDecision == null) { - final samplingContext = SentrySamplingContext( - transactionContext, customSamplingContext ?? {}); - - samplingDecision = _tracesSampler.sample( - samplingContext, - // sampleRand is guaranteed not to be null here - propagationContext.sampleRand!, - ); - - // Persist the sampling decision within the transaction context - transactionContext.samplingDecision = samplingDecision; - } - - transactionContext.origin ??= SentryTraceOrigins.manual; - transactionContext.traceId = propagationContext.traceId; - - // Persist the "sampled" decision onto the propagation context the - // first time we obtain one for the current trace. - // Subsequent transactions do not affect the sampled flag. - propagationContext.applySamplingDecision(samplingDecision.sampled); - - SentryProfiler? profiler; - if (_profilerFactory != null && - _tracesSampler.sampleProfiling(samplingDecision)) { - profiler = _profilerFactory?.startProfiler(transactionContext); - } - - final tracer = SentryTracer( - transactionContext, - this, - startTimestamp: startTimestamp, - waitForChildren: waitForChildren ?? false, - autoFinishAfter: autoFinishAfter, - trimEnd: trimEnd ?? false, - onFinish: onFinish, - profiler: profiler, - ); - if (bindToScope ?? false) { - item.scope.span = tracer; - } - - return tracer; - } - - return NoOpSentrySpan(); - } - - @internal - void generateNewTrace() { - // Create a brand-new trace and reset the sampling flag and sampleRand so - // that the next root transaction can set it again. - scope.propagationContext.resetTrace(); - } - - /// Gets the current active transaction or span. - ISentrySpan? getSpan() { - ISentrySpan? span; - if (!_isEnabled) { - _options.log( - SentryLevel.warning, - "Instance is disabled and this 'getSpan' call is a no-op.", - ); - } else if (_options.isTracingEnabled()) { - final item = _peek(); - - span = item.scope.span; - } - - return span; - } - - @internal - Future captureTransaction( - SentryTransaction transaction, { - SentryTraceContextHeader? traceContext, - Hint? hint, - }) async { - var sentryId = SentryId.empty(); - - if (!_isEnabled) { - _options.log( - SentryLevel.warning, - "Instance is disabled and this 'captureTransaction' call is a no-op.", - ); - } else if (!_options.isTracingEnabled()) { - _options.log( - SentryLevel.info, - "Tracing is disabled and this 'captureTransaction' call is a no-op.", - ); - } else if (!transaction.finished) { - _options.log( - SentryLevel.warning, - 'Capturing unfinished transaction: ${transaction.eventId}', - ); - } else { - final item = _peek(); - - if (!transaction.sampled) { - _options.recorder.recordLostEvent( - DiscardReason.sampleRate, - DataCategory.transaction, - ); - _options.recorder.recordLostEvent( - DiscardReason.sampleRate, - DataCategory.span, - count: transaction.spans.length + 1, - ); - _options.log( - SentryLevel.warning, - 'Transaction ${transaction.eventId} was dropped due to sampling decision.', - ); - } else { - try { - sentryId = await item.client.captureTransaction( - transaction, - scope: item.scope, - traceContext: traceContext, - hint: hint, - ); - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'Error while capturing transaction with id: ${transaction.eventId}', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } - } - } - return sentryId; - } - - @internal - void setSpanContext( - dynamic throwable, - ISentrySpan span, - String transaction, - ) => - _throwableToSpan.add(throwable, span, transaction); - - @internal - SentryProfilerFactory? get profilerFactory => _profilerFactory; - - @internal - set profilerFactory(SentryProfilerFactory? value) => _profilerFactory = value; - - SentryProfilerFactory? _profilerFactory; - - @internal - Map> get lifecycleCallbacks => - _peek().client.lifeCycleRegistry.lifecycleCallbacks; - - @internal - void registerSdkLifecycleCallback( - SdkLifecycleCallback callback) { - _peek().client.lifeCycleRegistry.registerCallback(callback); - } - - @internal - void removeSdkLifecycleCallback( - SdkLifecycleCallback callback) { - _peek().client.lifeCycleRegistry.removeCallback(callback); - } - - SentryEvent _assignTraceContext(SentryEvent event) { - // assign trace context - if (event.throwable != null && event.contexts.trace == null) { - // set span to event.contexts.trace - final pair = _throwableToSpan.get(event.throwable); - if (pair != null) { - final span = pair.key; - final spanContext = span.context; - event.contexts.trace = spanContext.toTraceContext( - sampled: span.samplingDecision?.sampled, - ); - - // set transaction name to event.transaction - event.transaction ??= pair.value; - } - } - return event; - } -} - -class _StackItem { - SentryClient client; - - final Scope scope; - - _StackItem(this.client, this.scope); -} - -class _WeakMap { - final _expando = Expando(); - - final SentryOptions _options; - - final throwableHandler = UnsupportedThrowablesHandler(); - - _WeakMap(this._options); - - void add( - dynamic throwable, - ISentrySpan span, - String transaction, - ) { - if (throwable == null) { - return; - } - throwable = throwableHandler.wrapIfUnsupportedType(throwable); - try { - if (_expando[throwable] == null) { - _expando[throwable] = MapEntry(span, transaction); - } - } catch (exception, stackTrace) { - _options.log( - SentryLevel.info, - 'Throwable type: ${throwable.runtimeType} is not supported for associating errors to a transaction.', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } - } - - MapEntry? get(dynamic throwable) { - if (throwable == null) { - return null; - } - throwable = throwableHandler.wrapIfUnsupportedType(throwable); - try { - return _expando[throwable] as MapEntry?; - } catch (exception, stackTrace) { - _options.log( - SentryLevel.info, - 'Throwable type: ${throwable.runtimeType} is not supported for associating errors to a transaction.', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } - return null; - } -} - -/// A handler for unsupported throwables used for Expando. -@visibleForTesting -class UnsupportedThrowablesHandler { - final _unsupportedTypes = {String, int, double, bool}; - final _unsupportedThrowables = {}; - - dynamic wrapIfUnsupportedType(dynamic throwable) { - if (_unsupportedTypes.contains(throwable.runtimeType)) { - throwable = _UnsupportedExceptionWrapper(Exception(throwable)); - _unsupportedThrowables.add(throwable); - } - return _unsupportedThrowables.lookup(throwable) ?? throwable; - } -} - -class _UnsupportedExceptionWrapper { - _UnsupportedExceptionWrapper(this.exception); - - final Exception exception; - - @override - bool operator ==(Object other) { - if (other is _UnsupportedExceptionWrapper) { - return other.exception.toString() == exception.toString(); - } - return false; - } - - @override - int get hashCode => exception.toString().hashCode; -} diff --git a/dart/lib/src/hub_adapter.dart b/dart/lib/src/hub_adapter.dart deleted file mode 100644 index 7595954444..0000000000 --- a/dart/lib/src/hub_adapter.dart +++ /dev/null @@ -1,219 +0,0 @@ -import 'dart:async'; - -import 'package:meta/meta.dart'; - -import 'hint.dart'; -import 'hub.dart'; -import 'profiling.dart'; -import 'protocol.dart'; -import 'protocol/sentry_feedback.dart'; -import 'scope.dart'; -import 'sdk_lifecycle_hooks.dart'; -import 'sentry.dart'; -import 'sentry_client.dart'; -import 'sentry_options.dart'; -import 'tracing.dart'; - -/// Hub adapter to make Integrations testable -class HubAdapter implements Hub { - const HubAdapter._(); - - static final HubAdapter _instance = HubAdapter._(); - - @override - @internal - SentryOptions get options => Sentry.currentHub.options; - - factory HubAdapter() { - return _instance; - } - - @override - Future addBreadcrumb(Breadcrumb crumb, {Hint? hint}) async => - await Sentry.addBreadcrumb(crumb, hint: hint); - - @override - void bindClient(SentryClient client) => Sentry.bindClient(client); - - @override - Future captureEvent( - SentryEvent event, { - dynamic stackTrace, - Hint? hint, - ScopeCallback? withScope, - }) => - Sentry.captureEvent( - event, - stackTrace: stackTrace, - hint: hint, - withScope: withScope, - ); - - @override - Future captureException( - dynamic throwable, { - dynamic stackTrace, - Hint? hint, - SentryMessage? message, - ScopeCallback? withScope, - }) => - Sentry.captureException( - throwable, - stackTrace: stackTrace, - hint: hint, - message: message, - withScope: withScope, - ); - - @override - Future captureMessage( - String? message, { - SentryLevel? level, - String? template, - List? params, - Hint? hint, - ScopeCallback? withScope, - }) => - Sentry.captureMessage( - message, - level: level, - template: template, - params: params, - hint: hint, - withScope: withScope, - ); - - @override - Hub clone() => Sentry.clone(); - - @override - Future close() => Sentry.close(); - - @override - FutureOr configureScope(ScopeCallback callback) => - Sentry.configureScope(callback); - - @override - bool get isEnabled => Sentry.isEnabled; - - @override - SentryId get lastEventId => Sentry.lastEventId; - - @override - Future captureTransaction( - SentryTransaction transaction, { - SentryTraceContextHeader? traceContext, - Hint? hint, - }) => - Sentry.currentHub.captureTransaction( - transaction, - traceContext: traceContext, - hint: hint, - ); - - @override - ISentrySpan? getSpan() => Sentry.currentHub.getSpan(); - - @override - ISentrySpan startTransactionWithContext( - SentryTransactionContext transactionContext, { - Map? customSamplingContext, - DateTime? startTimestamp, - bool? bindToScope, - bool? waitForChildren, - Duration? autoFinishAfter, - bool? trimEnd, - OnTransactionFinish? onFinish, - }) => - Sentry.startTransactionWithContext( - transactionContext, - customSamplingContext: customSamplingContext, - startTimestamp: startTimestamp, - bindToScope: bindToScope, - waitForChildren: waitForChildren, - autoFinishAfter: autoFinishAfter, - trimEnd: trimEnd, - onFinish: onFinish, - ); - - @override - ISentrySpan startTransaction( - String name, - String operation, { - String? description, - DateTime? startTimestamp, - bool? bindToScope, - bool? waitForChildren, - Duration? autoFinishAfter, - bool? trimEnd, - OnTransactionFinish? onFinish, - Map? customSamplingContext, - }) => - Sentry.startTransaction( - name, - operation, - description: description, - startTimestamp: startTimestamp, - bindToScope: bindToScope, - waitForChildren: waitForChildren, - autoFinishAfter: autoFinishAfter, - trimEnd: trimEnd, - onFinish: onFinish, - customSamplingContext: customSamplingContext, - ); - - @override - void generateNewTrace() => Sentry.currentHub.generateNewTrace(); - - @override - void setSpanContext( - dynamic throwable, - ISentrySpan span, - String transaction, - ) => - Sentry.currentHub.setSpanContext(throwable, span, transaction); - - @internal - @override - set profilerFactory(SentryProfilerFactory? value) => - Sentry.currentHub.profilerFactory = value; - - @internal - @override - SentryProfilerFactory? get profilerFactory => - Sentry.currentHub.profilerFactory; - - @override - Scope get scope => Sentry.currentHub.scope; - - @override - Future captureFeedback( - SentryFeedback feedback, { - Hint? hint, - ScopeCallback? withScope, - }) => - Sentry.currentHub.captureFeedback( - feedback, - hint: hint, - withScope: withScope, - ); - - @override - FutureOr captureLog(SentryLog log) => Sentry.currentHub.captureLog(log); - - @override - Map> get lifecycleCallbacks => - Sentry.currentHub.lifecycleCallbacks; - - @override - void registerSdkLifecycleCallback( - SdkLifecycleCallback callback) { - Sentry.currentHub.registerSdkLifecycleCallback(callback); - } - - @override - void removeSdkLifecycleCallback( - SdkLifecycleCallback callback) { - Sentry.currentHub.removeSdkLifecycleCallback(callback); - } -} diff --git a/dart/lib/src/integration.dart b/dart/lib/src/integration.dart deleted file mode 100644 index 570388bada..0000000000 --- a/dart/lib/src/integration.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:async'; - -import 'hub.dart'; -import 'sentry_options.dart'; - -/// Code that provides middlewares, bindings or hooks into certain frameworks or environments, -/// along with code that inserts those bindings and activates them. -abstract class Integration { - /// A Callable method for the Integration interface - FutureOr call(Hub hub, T options); - - /// NoOp by default : only closeable integrations need to override - FutureOr close() {} -} diff --git a/dart/lib/src/invalid_sentry_trace_header_exception.dart b/dart/lib/src/invalid_sentry_trace_header_exception.dart deleted file mode 100644 index 4b953fabfa..0000000000 --- a/dart/lib/src/invalid_sentry_trace_header_exception.dart +++ /dev/null @@ -1,7 +0,0 @@ -class InvalidSentryTraceHeaderException implements Exception { - final String _message; - InvalidSentryTraceHeaderException(this._message); - - @override - String toString() => 'Exception: $_message'; -} diff --git a/dart/lib/src/isolate_error_integration.dart b/dart/lib/src/isolate_error_integration.dart deleted file mode 100644 index ca617d6380..0000000000 --- a/dart/lib/src/isolate_error_integration.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'dart:isolate'; - -import 'hub.dart'; -import 'integration.dart'; -import 'sentry_isolate_extension.dart'; -import 'sentry_options.dart'; - -class IsolateErrorIntegration implements Integration { - RawReceivePort? _receivePort; - - @override - void call(Hub hub, SentryOptions options) { - _receivePort = Isolate.current.addSentryErrorListener(); - options.sdk.addIntegration('isolateErrorIntegration'); - } - - @override - void close() { - final receivePort = _receivePort; - if (receivePort != null) { - receivePort.close(); - Isolate.current.removeSentryErrorListener(receivePort); - } - } -} diff --git a/dart/lib/src/load_dart_debug_images_integration.dart b/dart/lib/src/load_dart_debug_images_integration.dart deleted file mode 100644 index 7d98573019..0000000000 --- a/dart/lib/src/load_dart_debug_images_integration.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'dart:typed_data'; - -import 'package:meta/meta.dart'; - -import 'event_processor.dart'; -import 'hint.dart'; -import 'hub.dart'; -import 'integration.dart'; -import 'protocol/debug_image.dart'; -import 'protocol/debug_meta.dart'; -import 'protocol/sentry_event.dart'; -import 'protocol/sentry_level.dart'; -import 'protocol/sentry_stack_trace.dart'; -import 'sentry_options.dart'; - -class LoadDartDebugImagesIntegration extends Integration { - static const integrationName = 'LoadDartDebugImages'; - - @override - void call(Hub hub, SentryOptions options) { - if (options.enableDartSymbolication && - (options.runtimeChecker.isAppObfuscated() || - options.runtimeChecker.isSplitDebugInfoBuild()) && - !options.platform.isWeb) { - options.addEventProcessor( - LoadDartDebugImagesIntegrationEventProcessor(options)); - options.sdk.addIntegration(integrationName); - } - } -} - -@internal -class LoadDartDebugImagesIntegrationEventProcessor implements EventProcessor { - LoadDartDebugImagesIntegrationEventProcessor(this._options); - - final SentryOptions _options; - - // We don't need to always create the debug image, so we cache it here. - DebugImage? _debugImage; - - @override - Future apply(SentryEvent event, Hint hint) async { - final stackTrace = event.stacktrace; - if (stackTrace != null) { - final debugImage = getAppDebugImage(stackTrace); - if (debugImage != null) { - if (event.debugMeta != null) { - event.debugMeta?.addDebugImage(debugImage); - } else { - event.debugMeta = DebugMeta(images: [debugImage]); - } - } - } - - return event; - } - - DebugImage? getAppDebugImage(SentryStackTrace stackTrace) { - // Don't return the debug image if the stack trace doesn't have native info. - if (stackTrace.baseAddr == null || - stackTrace.buildId == null || - !stackTrace.frames.any((f) => f.platform == 'native')) { - return null; - } - try { - _debugImage ??= createDebugImage(stackTrace); - } catch (e, stack) { - _options.log( - SentryLevel.info, - "Couldn't add Dart debug image to event. The event will still be reported.", - exception: e, - stackTrace: stack, - ); - if (_options.automatedTestMode) { - rethrow; - } - } - return _debugImage; - } - - @visibleForTesting - DebugImage? createDebugImage(SentryStackTrace stackTrace) { - if (stackTrace.buildId == null || stackTrace.baseAddr == null) { - _options.log(SentryLevel.warning, - 'Cannot create DebugImage without a build ID and image base address.'); - return null; - } - - // Type and DebugID are required for proper symbolication - late final String type; - late final String debugId; - - // CodeFile is required so that the debug image shows up properly in the UI. - // It doesn't need to exist and is not used for symbolication. - late final String codeFile; - - final platform = _options.platform; - - if (platform.isAndroid || platform.isWindows) { - type = 'elf'; - debugId = _convertBuildIdToDebugId(stackTrace.buildId!, Endian.host); - if (platform.isAndroid) { - codeFile = 'libapp.so'; - } else if (platform.isWindows) { - codeFile = 'data/app.so'; - } - } else if (platform.isIOS || platform.isMacOS) { - type = 'macho'; - debugId = _formatHexToUuid(stackTrace.buildId!); - codeFile = 'App.Framework/App'; - } else { - _options.log( - SentryLevel.warning, - 'Unsupported platform for creating Dart debug images.', - ); - return null; - } - - return DebugImage( - type: type, - imageAddr: stackTrace.baseAddr, - debugId: debugId, - codeId: stackTrace.buildId, - codeFile: codeFile, - ); - } - - /// See https://github.com/getsentry/symbolic/blob/7dc28dd04c06626489c7536cfe8c7be8f5c48804/symbolic-debuginfo/src/elf.rs#L709-L734 - /// Converts an ELF object identifier into a `DebugId`. - /// - /// The identifier data is first truncated or extended to match 16 byte size of - /// Uuids. If the data is declared in little endian, the first three Uuid fields - /// are flipped to match the big endian expected by the breakpad processor. - /// - /// The `DebugId::appendix` field is always `0` for ELF. - String _convertBuildIdToDebugId(String buildId, Endian endian) { - // Make sure that we have exactly UUID_SIZE bytes available - const uuidSize = 16 * 2; - final data = Uint8List(uuidSize); - final len = buildId.length.clamp(0, uuidSize); - data.setAll(0, buildId.codeUnits.take(len)); - - if (endian == Endian.little) { - // The file ELF file targets a little endian architecture. Convert to - // network byte order (big endian) to match the Breakpad processor's - // expectations. For big endian object files, this is not needed. - // To manipulate this as hex, we create an Uint16 view. - final data16 = Uint16List.view(data.buffer); - data16.setRange(0, 4, data16.sublist(0, 4).reversed); - data16.setRange(4, 6, data16.sublist(4, 6).reversed); - data16.setRange(6, 8, data16.sublist(6, 8).reversed); - } - return _formatHexToUuid(String.fromCharCodes(data)); - } - - String _formatHexToUuid(String hex) { - if (hex.length == 36) { - return hex; - } - if (hex.length != 32) { - throw ArgumentError.value(hex, 'hexUUID', - 'Hex input must be a 32-character hexadecimal string'); - } - - return '${hex.substring(0, 8)}-' - '${hex.substring(8, 12)}-' - '${hex.substring(12, 16)}-' - '${hex.substring(16, 20)}-' - '${hex.substring(20)}'; - } -} diff --git a/dart/lib/src/logs_enricher_integration.dart b/dart/lib/src/logs_enricher_integration.dart deleted file mode 100644 index 1885a95c19..0000000000 --- a/dart/lib/src/logs_enricher_integration.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:async'; -import 'package:meta/meta.dart'; - -import 'sdk_lifecycle_hooks.dart'; -import 'utils/os_utils.dart'; -import 'integration.dart'; -import 'hub.dart'; -import 'protocol/sentry_log_attribute.dart'; -import 'sentry_options.dart'; - -@internal -class LogsEnricherIntegration extends Integration { - static const integrationName = 'LogsEnricher'; - - @override - FutureOr call(Hub hub, SentryOptions options) { - if (options.enableLogs) { - hub.registerSdkLifecycleCallback( - (event) async { - final os = getSentryOperatingSystem(); - - if (os.name != null) { - event.log.attributes['os.name'] = SentryLogAttribute.string( - os.name ?? '', - ); - } - if (os.version != null) { - event.log.attributes['os.version'] = SentryLogAttribute.string( - os.version ?? '', - ); - } - }, - ); - options.sdk.addIntegration(integrationName); - } - } -} diff --git a/dart/lib/src/noop_client.dart b/dart/lib/src/noop_client.dart deleted file mode 100644 index 2ad9c1a76d..0000000000 --- a/dart/lib/src/noop_client.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; -import 'dart:convert'; -import 'package:http/http.dart'; - -class NoOpClient implements Client { - NoOpClient._(); - - static final NoOpClient _instance = NoOpClient._(); - - static final Future _response = Future.value(Response('', 500)); - static final Future _string = Future.value(''); - static final Future _intList = Future.value(Uint8List(0)); - static final Future _streamedResponse = - Future.value(StreamedResponse(Stream.empty(), 500)); - - factory NoOpClient() { - return _instance; - } - - @override - void close() {} - - @override - Future delete( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _response; - - @override - Future get(url, {Map? headers}) => _response; - - @override - Future head(url, {Map? headers}) => _response; - - @override - Future patch(url, - {Map? headers, body, Encoding? encoding}) => - _response; - - @override - Future post(url, - {Map? headers, body, Encoding? encoding}) => - _response; - - @override - Future put(url, - {Map? headers, body, Encoding? encoding}) => - _response; - - @override - Future read(url, {Map? headers}) => _string; - - @override - Future readBytes(url, {Map? headers}) => _intList; - - @override - Future send(BaseRequest request) => _streamedResponse; -} diff --git a/dart/lib/src/noop_hub.dart b/dart/lib/src/noop_hub.dart deleted file mode 100644 index de5de1a2b7..0000000000 --- a/dart/lib/src/noop_hub.dart +++ /dev/null @@ -1,159 +0,0 @@ -import 'dart:async'; - -import 'package:meta/meta.dart'; - -import 'hint.dart'; -import 'hub.dart'; -import 'profiling.dart'; -import 'protocol.dart'; -import 'protocol/sentry_feedback.dart'; -import 'scope.dart'; -import 'sdk_lifecycle_hooks.dart'; -import 'sentry_client.dart'; -import 'sentry_options.dart'; -import 'tracing.dart'; - -class NoOpHub implements Hub { - NoOpHub._(); - - static final NoOpHub _instance = NoOpHub._(); - - final _options = SentryOptions.empty(); - - @override - @internal - SentryOptions get options => _options; - - factory NoOpHub() { - return _instance; - } - - @override - void bindClient(SentryClient client) {} - - @override - Future captureEvent( - SentryEvent event, { - dynamic stackTrace, - Hint? hint, - ScopeCallback? withScope, - }) async => - SentryId.empty(); - - @override - Future captureException( - dynamic throwable, { - dynamic stackTrace, - Hint? hint, - SentryMessage? message, - ScopeCallback? withScope, - }) async => - SentryId.empty(); - - @override - Future captureMessage( - String? message, { - SentryLevel? level, - String? template, - List? params, - Hint? hint, - ScopeCallback? withScope, - }) async => - SentryId.empty(); - - @override - Hub clone() => this; - - @override - Future close() async {} - - @override - void configureScope(callback) {} - - @override - bool get isEnabled => false; - - @override - SentryId get lastEventId => SentryId.empty(); - - @override - Future addBreadcrumb(Breadcrumb crumb, {Hint? hint}) async {} - - @override - Future captureTransaction( - SentryTransaction transaction, { - SentryTraceContextHeader? traceContext, - Hint? hint, - }) async => - SentryId.empty(); - - @override - Future captureFeedback( - SentryFeedback feedback, { - Hint? hint, - ScopeCallback? withScope, - }) async => - SentryId.empty(); - - @override - FutureOr captureLog(SentryLog log) async {} - - @override - ISentrySpan startTransaction( - String name, - String operation, { - String? description, - DateTime? startTimestamp, - bool? bindToScope, - bool? waitForChildren, - Duration? autoFinishAfter, - bool? trimEnd, - OnTransactionFinish? onFinish, - Map? customSamplingContext, - }) => - NoOpSentrySpan(); - - @override - ISentrySpan startTransactionWithContext( - SentryTransactionContext transactionContext, { - Map? customSamplingContext, - DateTime? startTimestamp, - bool? bindToScope, - bool? waitForChildren, - Duration? autoFinishAfter, - bool? trimEnd, - OnTransactionFinish? onFinish, - }) => - NoOpSentrySpan(); - - @override - void generateNewTrace() {} - - @override - ISentrySpan? getSpan() => null; - - @override - void setSpanContext(throwable, ISentrySpan span, String transaction) {} - - @internal - @override - set profilerFactory(SentryProfilerFactory? value) {} - - @internal - @override - SentryProfilerFactory? get profilerFactory => null; - - @override - Scope get scope => Scope(_options); - - @override - Map> get lifecycleCallbacks => {}; - - @override - void registerSdkLifecycleCallback( - SdkLifecycleCallback callback) {} - - @override - void removeSdkLifecycleCallback( - SdkLifecycleCallback callback) {} -} diff --git a/dart/lib/src/noop_isolate_error_integration.dart b/dart/lib/src/noop_isolate_error_integration.dart deleted file mode 100644 index 681b97c1a6..0000000000 --- a/dart/lib/src/noop_isolate_error_integration.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'hub.dart'; -import 'integration.dart'; -import 'sentry_options.dart'; - -/// NoOp web integration : isolate doesnt' work in browser -class IsolateErrorIntegration extends Integration { - @override - void call(Hub hub, SentryOptions options) {} - - void handleIsolateError( - Hub hub, - SentryOptions options, - dynamic error, - ) {} -} diff --git a/dart/lib/src/noop_log_batcher.dart b/dart/lib/src/noop_log_batcher.dart deleted file mode 100644 index 7d5c94523d..0000000000 --- a/dart/lib/src/noop_log_batcher.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'dart:async'; - -import 'sentry_log_batcher.dart'; -import 'protocol/sentry_log.dart'; - -class NoopLogBatcher implements SentryLogBatcher { - @override - FutureOr addLog(SentryLog log) {} - - @override - Future flush() async {} -} diff --git a/dart/lib/src/noop_sentry_client.dart b/dart/lib/src/noop_sentry_client.dart deleted file mode 100644 index a0badcc7d9..0000000000 --- a/dart/lib/src/noop_sentry_client.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'dart:async'; - -import 'hint.dart'; -import 'protocol.dart'; -import 'protocol/sentry_feedback.dart'; -import 'scope.dart'; -import 'sdk_lifecycle_hooks.dart'; -import 'sentry_client.dart'; -import 'sentry_envelope.dart'; -import 'sentry_options.dart'; -import 'sentry_trace_context_header.dart'; - -class NoOpSentryClient implements SentryClient { - NoOpSentryClient._(); - - static final NoOpSentryClient _instance = NoOpSentryClient._(); - - factory NoOpSentryClient() { - return _instance; - } - - @override - Future captureEvent( - SentryEvent event, { - dynamic stackTrace, - Scope? scope, - Hint? hint, - }) async => - SentryId.empty(); - - @override - Future captureException( - dynamic exception, { - dynamic stackTrace, - Scope? scope, - Hint? hint, - }) async => - SentryId.empty(); - - @override - Future captureMessage( - String? message, { - SentryLevel? level, - String? template, - List? params, - Scope? scope, - Hint? hint, - }) async => - SentryId.empty(); - - @override - Future captureEnvelope(SentryEnvelope envelope) async => - SentryId.empty(); - - @override - Future close() async {} - - @override - Future captureTransaction( - SentryTransaction transaction, { - Scope? scope, - SentryTraceContextHeader? traceContext, - Hint? hint, - }) async => - SentryId.empty(); - - @override - Future captureFeedback(SentryFeedback feedback, - {Scope? scope, Hint? hint}) async => - SentryId.empty(); - - @override - FutureOr captureLog(SentryLog log, {Scope? scope}) async {} - - final _lifeCycleRegistry = SdkLifecycleRegistry(SentryOptions.empty()); - - @override - SdkLifecycleRegistry get lifeCycleRegistry => _lifeCycleRegistry; -} diff --git a/dart/lib/src/noop_sentry_span.dart b/dart/lib/src/noop_sentry_span.dart deleted file mode 100644 index 0038d0954d..0000000000 --- a/dart/lib/src/noop_sentry_span.dart +++ /dev/null @@ -1,103 +0,0 @@ -import 'hint.dart'; -import 'protocol.dart'; -import 'tracing.dart'; -import 'utils.dart'; - -class NoOpSentrySpan extends ISentrySpan { - NoOpSentrySpan._(); - - static final _instance = NoOpSentrySpan._(); - - static final _spanContext = SentrySpanContext( - traceId: SentryId.empty(), - spanId: SpanId.empty(), - operation: 'noop', - ); - - static final _header = SentryTraceHeader( - SentryId.empty(), - SpanId.empty(), - sampled: false, - ); - - static final _timestamp = getUtcDateTime(); - - factory NoOpSentrySpan() { - return _instance; - } - - @override - Future finish({ - SpanStatus? status, - DateTime? endTimestamp, - Hint? hint, - }) async {} - - @override - void removeData(String key) {} - - @override - void removeTag(String key) {} - - @override - void setData(String key, value) {} - - @override - void setTag(String key, String value) {} - - @override - ISentrySpan startChild( - String operation, { - String? description, - DateTime? startTimestamp, - }) => - NoOpSentrySpan(); - - @override - SentrySpanContext get context => _spanContext; - - @override - String? get origin => null; - - @override - set origin(String? origin) {} - - @override - SpanStatus? get status => null; - - @override - DateTime get startTimestamp => _timestamp; - - @override - DateTime? get endTimestamp => null; - - @override - bool get finished => false; - - @override - dynamic get throwable => null; - - @override - set throwable(throwable) {} - - @override - set status(SpanStatus? status) {} - - @override - SentryTraceHeader toSentryTrace() => _header; - - @override - void setMeasurement(String name, num value, {SentryMeasurementUnit? unit}) {} - - @override - SentryBaggageHeader? toBaggageHeader() => null; - - @override - SentryTraceContextHeader? traceContext() => null; - - @override - SentryTracesSamplingDecision? get samplingDecision => null; - - @override - void scheduleFinish() {} -} diff --git a/dart/lib/src/origin.dart b/dart/lib/src/origin.dart deleted file mode 100644 index 65835e7e0a..0000000000 --- a/dart/lib/src/origin.dart +++ /dev/null @@ -1 +0,0 @@ -export 'origin_io.dart' if (dart.library.js_interop) 'origin_web.dart'; diff --git a/dart/lib/src/origin_io.dart b/dart/lib/src/origin_io.dart deleted file mode 100644 index 0d02495ae3..0000000000 --- a/dart/lib/src/origin_io.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// request origin, empty out of browser context -String eventOrigin = ''; diff --git a/dart/lib/src/origin_web.dart b/dart/lib/src/origin_web.dart deleted file mode 100644 index 0653054b7a..0000000000 --- a/dart/lib/src/origin_web.dart +++ /dev/null @@ -1,4 +0,0 @@ -import 'package:web/web.dart'; - -/// request origin, used for browser stacktrace -String get eventOrigin => '${window.location.origin}/'; diff --git a/dart/lib/src/performance_collector.dart b/dart/lib/src/performance_collector.dart deleted file mode 100644 index 736534fb03..0000000000 --- a/dart/lib/src/performance_collector.dart +++ /dev/null @@ -1,13 +0,0 @@ -import '../sentry.dart'; - -abstract class PerformanceCollector {} - -/// Used for collecting continuous data about vitals (slow, frozen frames, etc.) -/// during a transaction/span. -abstract class PerformanceContinuousCollector extends PerformanceCollector { - Future onSpanStarted(ISentrySpan span); - - Future onSpanFinished(ISentrySpan span, DateTime endTimestamp); - - void clear(); -} diff --git a/dart/lib/src/platform/_io_platform.dart b/dart/lib/src/platform/_io_platform.dart deleted file mode 100644 index fa8f0f5ea2..0000000000 --- a/dart/lib/src/platform/_io_platform.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:io' as io; - -import 'platform.dart'; - -/// [Platform] implementation that delegates directly to `dart:io`. -class PlatformBase { - const PlatformBase(); - - OperatingSystem get operatingSystem { - switch (io.Platform.operatingSystem) { - case 'macos': - return OperatingSystem.macos; - case 'windows': - return OperatingSystem.windows; - case 'linux': - return OperatingSystem.linux; - case 'android': - return OperatingSystem.android; - case 'ios': - return OperatingSystem.ios; - case 'fuchsia': - return OperatingSystem.fuchsia; - default: - return OperatingSystem.unknown; - } - } - - String? get operatingSystemVersion => io.Platform.operatingSystemVersion; - - String get localHostname => io.Platform.localHostname; - - bool get isWeb => false; -} diff --git a/dart/lib/src/platform/_web_platform.dart b/dart/lib/src/platform/_web_platform.dart deleted file mode 100644 index 56325fa25b..0000000000 --- a/dart/lib/src/platform/_web_platform.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:web/web.dart' as web; - -import 'platform.dart'; - -/// [Platform] implementation that delegates to `dart:web`. -class PlatformBase { - const PlatformBase(); - - bool get isWeb => true; - - OperatingSystem get operatingSystem { - final navigatorPlatform = web.window.navigator.platform.toLowerCase(); - if (navigatorPlatform.startsWith('mac')) { - return OperatingSystem.macos; - } - if (navigatorPlatform.startsWith('win')) { - return OperatingSystem.windows; - } - if (navigatorPlatform.contains('iphone') || - navigatorPlatform.contains('ipad') || - navigatorPlatform.contains('ipod')) { - return OperatingSystem.ios; - } - if (navigatorPlatform.contains('android')) { - return OperatingSystem.android; - } - if (navigatorPlatform.contains('fuchsia')) { - return OperatingSystem.fuchsia; - } - - // Since some phones can report a window.navigator.platform as Linux, fall - // back to use CSS to disambiguate Android vs Linux desktop. If the CSS - // indicates that a device has a "fine pointer" (mouse) as the primary - // pointing device, then we'll assume desktop linux, and otherwise we'll - // assume Android. - if (web.window.matchMedia('only screen and (pointer: fine)').matches) { - return OperatingSystem.linux; - } - return OperatingSystem.android; - } - - String? get operatingSystemVersion => null; - - String get localHostname => web.window.location.hostname; -} diff --git a/dart/lib/src/platform/mock_platform.dart b/dart/lib/src/platform/mock_platform.dart deleted file mode 100644 index 99b3ef81cc..0000000000 --- a/dart/lib/src/platform/mock_platform.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'platform.dart'; - -class MockPlatform extends Platform { - @override - late final bool isWeb; - - @override - late final OperatingSystem operatingSystem; - - @override - late final String? operatingSystemVersion; - - @override - late final bool supportsNativeIntegration; - - MockPlatform( - {OperatingSystem? operatingSystem, - String? operatingSystemVersion, - bool? isWeb, - bool? supportsNativeIntegration}) { - this.isWeb = isWeb ?? super.isWeb; - this.operatingSystem = operatingSystem ?? super.operatingSystem; - this.operatingSystemVersion = - operatingSystemVersion ?? super.operatingSystemVersion; - this.supportsNativeIntegration = - supportsNativeIntegration ?? super.supportsNativeIntegration; - } - - factory MockPlatform.android({bool isWeb = false}) { - return MockPlatform(operatingSystem: OperatingSystem.android, isWeb: isWeb); - } - - factory MockPlatform.iOS({bool isWeb = false}) { - return MockPlatform(operatingSystem: OperatingSystem.ios, isWeb: isWeb); - } - - factory MockPlatform.macOS({bool isWeb = false}) { - return MockPlatform(operatingSystem: OperatingSystem.macos, isWeb: isWeb); - } - - factory MockPlatform.linux({bool isWeb = false}) { - return MockPlatform(operatingSystem: OperatingSystem.linux, isWeb: isWeb); - } - - factory MockPlatform.windows({bool isWeb = false}) { - return MockPlatform(operatingSystem: OperatingSystem.windows, isWeb: isWeb); - } - - factory MockPlatform.fuchsia({bool isWeb = false}) { - return MockPlatform(operatingSystem: OperatingSystem.fuchsia, isWeb: isWeb); - } -} diff --git a/dart/lib/src/platform/platform.dart b/dart/lib/src/platform/platform.dart deleted file mode 100644 index ea0ed3fdf7..0000000000 --- a/dart/lib/src/platform/platform.dart +++ /dev/null @@ -1,30 +0,0 @@ -import '_io_platform.dart' if (dart.library.js_interop) '_web_platform.dart' - as impl; - -class Platform extends impl.PlatformBase { - const Platform(); - - bool get isLinux => operatingSystem == OperatingSystem.linux; - - bool get isMacOS => operatingSystem == OperatingSystem.macos; - - bool get isWindows => operatingSystem == OperatingSystem.windows; - - bool get isAndroid => operatingSystem == OperatingSystem.android; - - bool get isIOS => operatingSystem == OperatingSystem.ios; - - bool get isFuchsia => operatingSystem == OperatingSystem.fuchsia; - - bool get supportsNativeIntegration => !isFuchsia; -} - -enum OperatingSystem { - android, - fuchsia, - ios, - linux, - macos, - windows, - unknown, -} diff --git a/dart/lib/src/profiling.dart b/dart/lib/src/profiling.dart deleted file mode 100644 index d0ed997313..0000000000 --- a/dart/lib/src/profiling.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'dart:async'; - -import 'package:meta/meta.dart'; - -import '../sentry.dart'; - -@internal -abstract class SentryProfilerFactory { - SentryProfiler? startProfiler(SentryTransactionContext context); -} - -@internal -abstract class SentryProfiler { - Future finishFor(SentryTransaction transaction); - void dispose(); -} - -// See https://develop.sentry.dev/sdk/profiles/ -@internal -abstract class SentryProfileInfo { - SentryEnvelopeItem asEnvelopeItem(); -} diff --git a/dart/lib/src/propagation_context.dart b/dart/lib/src/propagation_context.dart deleted file mode 100644 index b75a897383..0000000000 --- a/dart/lib/src/propagation_context.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../sentry.dart'; - -@internal -class PropagationContext { - /// Either represents the incoming `traceId` or the `traceId` generated by the current SDK, if there was no incoming trace. - SentryId traceId = SentryId.newId(); - - /// The dynamic sampling context. - SentryBaggage? baggage; - - bool? _sampled; - - /// Indicates whether the current trace is sampled or not. - /// - /// This flag follows the lifecycle of a trace: - /// * It starts as `null` (undecided). - /// * The **first** transaction that receives a sampling decision (root - /// transaction) sets the flag to the decided value. Subsequent - /// transactions for the same trace MUST NOT change the value. - /// * When a new trace is started (i.e. when a new `traceId` is generated), - /// the flag is reset back to `null`. - /// - /// The flag is propagated via the `sentry-trace` header so that downstream - /// services can honour the original sampling decision. - bool? get sampled => _sampled; - - /// Applies the sampling decision exactly once per trace. - void applySamplingDecision(bool sampled) { - _sampled ??= sampled; - } - - /// Random number generated for sampling decisions. - /// - /// This value must be generated **once per trace** and reused across all - /// child spans and transactions that belong to the same trace. It is reset - /// whenever a new trace is started. - double? sampleRand; - - /// Starts a brand-new trace (new ID, new sampling value & sampled state). - void resetTrace() { - traceId = SentryId.newId(); - sampleRand = null; - _sampled = null; - } - - /// Baggage header to attach to http headers. - SentryBaggageHeader? toBaggageHeader() => - baggage != null ? SentryBaggageHeader.fromBaggage(baggage!) : null; - - /// Sentry trace header to attach to http headers. - SentryTraceHeader toSentryTrace() => generateSentryTraceHeader( - traceId: traceId, - sampled: sampled, - ); -} diff --git a/dart/lib/src/protocol.dart b/dart/lib/src/protocol.dart deleted file mode 100644 index 07b7e0b30b..0000000000 --- a/dart/lib/src/protocol.dart +++ /dev/null @@ -1,46 +0,0 @@ -export 'protocol/breadcrumb.dart'; -export 'protocol/contexts.dart'; -export 'protocol/debug_image.dart'; -export 'protocol/debug_meta.dart'; -export 'protocol/dsn.dart'; -export 'protocol/max_body_size.dart'; -export 'protocol/mechanism.dart'; -export 'protocol/sdk_info.dart'; -export 'protocol/sdk_version.dart'; -export 'protocol/sentry_app.dart'; -export 'protocol/sentry_baggage_header.dart'; -export 'protocol/sentry_browser.dart'; -export 'protocol/sentry_culture.dart'; -export 'protocol/sentry_device.dart'; -export 'protocol/sentry_event.dart'; -export 'protocol/sentry_exception.dart'; -export 'protocol/sentry_geo.dart'; -export 'protocol/sentry_gpu.dart'; -export 'protocol/sentry_id.dart'; -export 'protocol/sentry_level.dart'; -export 'protocol/sentry_message.dart'; -export 'protocol/sentry_operating_system.dart'; -export 'protocol/sentry_package.dart'; -export 'protocol/sentry_request.dart'; -export 'protocol/sentry_response.dart'; -export 'protocol/sentry_runtime.dart'; -export 'protocol/sentry_span.dart'; -export 'protocol/sentry_stack_frame.dart'; -export 'protocol/sentry_stack_trace.dart'; -export 'protocol/sentry_thread.dart'; -export 'protocol/sentry_trace_context.dart'; -export 'protocol/sentry_trace_header.dart'; -export 'protocol/sentry_transaction.dart'; -export 'protocol/sentry_transaction_info.dart'; -export 'protocol/sentry_transaction_name_source.dart'; -export 'protocol/sentry_user.dart'; -export 'protocol/sentry_view_hierarchy.dart'; -export 'protocol/sentry_view_hierarchy_element.dart'; -export 'protocol/span_id.dart'; -export 'protocol/span_status.dart'; -export 'sentry_event_like.dart'; -export 'protocol/sentry_feature_flag.dart'; -export 'protocol/sentry_feature_flags.dart'; -export 'protocol/sentry_log.dart'; -export 'protocol/sentry_log_level.dart'; -export 'protocol/sentry_log_attribute.dart'; diff --git a/dart/lib/src/protocol/access_aware_map.dart b/dart/lib/src/protocol/access_aware_map.dart deleted file mode 100644 index c8ea7b2395..0000000000 --- a/dart/lib/src/protocol/access_aware_map.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'dart:collection'; - -import 'package:meta/meta.dart'; - -@internal -class AccessAwareMap extends MapBase { - AccessAwareMap(this._map); - - final Map _map; - final Set _accessedKeysWithValues = {}; - - Set get accessedKeysWithValues => _accessedKeysWithValues; - - @override - V? operator [](Object? key) { - if (key is String && _map.containsKey(key)) { - _accessedKeysWithValues.add(key); - } - return _map[key]; - } - - @override - void operator []=(String key, V value) { - _map[key] = value; - } - - @override - void clear() { - _map.clear(); - _accessedKeysWithValues.clear(); - } - - @override - Iterable get keys => _map.keys; - - @override - V? remove(Object? key) { - return _map.remove(key); - } - - Map? notAccessed() { - if (_accessedKeysWithValues.length == _map.length) { - return null; - } - Map unknown = _map.keys - .where((key) => !_accessedKeysWithValues.contains(key)) - .fold>({}, (map, key) { - map[key] = _map[key]; - return map; - }); - return unknown.isNotEmpty ? unknown : null; - } -} diff --git a/dart/lib/src/protocol/breadcrumb.dart b/dart/lib/src/protocol/breadcrumb.dart deleted file mode 100644 index 5cb0f6e4f0..0000000000 --- a/dart/lib/src/protocol/breadcrumb.dart +++ /dev/null @@ -1,222 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../utils.dart'; -import '../protocol.dart'; -import 'access_aware_map.dart'; - -/// Structured data to describe more information prior to the event captured. -/// See `Sentry.captureEvent()`. -/// -/// The outgoing JSON representation is: -/// -/// ``` -/// { -/// "timestamp": 1000 -/// "message": "message", -/// "category": "category", -/// "data": {"key": "value"}, -/// "level": "info", -/// "type": "default" -/// } -/// ``` -/// See also: -/// * https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/ -class Breadcrumb { - /// Creates a breadcrumb that can be attached to an [SentryEvent]. - Breadcrumb({ - this.message, - DateTime? timestamp, - this.category, - this.data, - SentryLevel? level, - this.type, - this.unknown, - }) : timestamp = timestamp ?? getUtcDateTime(), - level = level ?? SentryLevel.info; - - factory Breadcrumb.http({ - required Uri url, - required String method, - int? statusCode, - String? reason, - Duration? requestDuration, - SentryLevel? level, - DateTime? timestamp, - - // Size of the request body in bytes - int? requestBodySize, - - // Size of the response body in bytes - int? responseBodySize, - String? httpQuery, - String? httpFragment, - }) { - // The timestamp is used as the request-end time, so we need to set it right - // now and not rely on the default constructor. - timestamp ??= getUtcDateTime(); - - return Breadcrumb( - type: 'http', - category: 'http', - level: level, - timestamp: timestamp, - data: { - 'url': url.toString(), - 'method': method, - if (statusCode != null) 'status_code': statusCode, - if (reason != null) 'reason': reason, - if (requestDuration != null) 'duration': requestDuration.toString(), - if (requestBodySize != null) 'request_body_size': requestBodySize, - if (responseBodySize != null) 'response_body_size': responseBodySize, - if (httpQuery != null) 'http.query': httpQuery, - if (httpFragment != null) 'http.fragment': httpFragment, - if (requestDuration != null) - 'start_timestamp': - timestamp.millisecondsSinceEpoch - requestDuration.inMilliseconds, - if (requestDuration != null) - 'end_timestamp': timestamp.millisecondsSinceEpoch, - }, - ); - } - - factory Breadcrumb.console({ - String? message, - SentryLevel? level, - DateTime? timestamp, - Map? data, - }) { - return Breadcrumb( - message: message, - level: level, - category: 'console', - type: 'debug', - timestamp: timestamp, - data: data, - ); - } - - factory Breadcrumb.userInteraction({ - String? message, - SentryLevel? level, - DateTime? timestamp, - Map? data, - required String subCategory, - String? viewId, - String? viewClass, - }) { - return Breadcrumb( - message: message, - level: level, - category: 'ui.$subCategory', - type: 'user', - timestamp: timestamp, - data: { - if (viewId != null) 'view.id': viewId, - if (viewClass != null) 'view.class': viewClass, - if (data != null) ...data, - }, - ); - } - - /// Describes the breadcrumb. - /// - /// This field is optional and may be set to null. - String? message; - - /// A dot-separated string describing the source of the breadcrumb, e.g. "ui.click". - /// - /// This field is optional and may be set to null. - String? category; - - /// Data associated with the breadcrumb. - /// - /// The contents depend on the [type] of breadcrumb. - /// - /// This field is optional and may be set to null. - /// - /// See also: - /// - /// * https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types - Map? data; - - /// Severity of the breadcrumb. - /// - /// This field is optional and may be set to null. - SentryLevel? level; - - /// Describes what type of breadcrumb this is. - /// - /// Possible values: "default", "http", "navigation". - /// - /// This field is optional and may be set to null. - /// - /// See also: - /// - /// * https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types - String? type; - - /// The time the breadcrumb was recorded. - /// - /// This field is required, it must not be null. - /// - /// The value is submitted to Sentry with second precision. - DateTime timestamp; - - @internal - final Map? unknown; - - /// Deserializes a [Breadcrumb] from JSON [Map]. - factory Breadcrumb.fromJson(Map jsonData) { - final json = AccessAwareMap(jsonData); - - final levelName = json['level']; - final timestamp = json['timestamp']; - - var data = json['data']; - if (data != null) { - data = Map.from(data as Map); - } - return Breadcrumb( - timestamp: timestamp != null ? DateTime.tryParse(timestamp) : null, - message: json['message'], - category: json['category'], - data: data, - level: levelName != null ? SentryLevel.fromName(levelName) : null, - type: json['type'], - unknown: json.notAccessed(), - ); - } - - /// Converts this breadcrumb to a map that can be serialized to JSON according - /// to the Sentry protocol. - Map toJson() { - return { - ...?unknown, - 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), - if (message != null) 'message': message, - if (category != null) 'category': category, - if (data?.isNotEmpty ?? false) 'data': data, - if (level != null) 'level': level!.name, - if (type != null) 'type': type, - }; - } - - @Deprecated('Assign values directly to the instance.') - Breadcrumb copyWith({ - String? message, - String? category, - Map? data, - SentryLevel? level, - String? type, - DateTime? timestamp, - }) => - Breadcrumb( - message: message ?? this.message, - category: category ?? this.category, - data: data ?? this.data, - level: level ?? this.level, - type: type ?? this.type, - timestamp: timestamp ?? this.timestamp, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/contexts.dart b/dart/lib/src/protocol/contexts.dart deleted file mode 100644 index 1a68843baf..0000000000 --- a/dart/lib/src/protocol/contexts.dart +++ /dev/null @@ -1,349 +0,0 @@ -import 'dart:collection'; -import 'package:meta/meta.dart'; - -import '../protocol.dart'; -import 'sentry_feedback.dart'; - -/// The context interfaces provide additional context data. -/// -/// Typically this is data related to the Device, OS, Runtime, App, -/// Browser, GPU and State context. -/// -/// See also: https://develop.sentry.dev/sdk/event-payloads/contexts/. -class Contexts extends MapView { - Contexts({ - SentryDevice? device, - SentryOperatingSystem? operatingSystem, - List? runtimes, - SentryApp? app, - SentryBrowser? browser, - SentryGpu? gpu, - SentryCulture? culture, - SentryTraceContext? trace, - SentryResponse? response, - SentryFeedback? feedback, - SentryFeatureFlags? flags, - }) : super({ - SentryDevice.type: device, - SentryOperatingSystem.type: operatingSystem, - SentryRuntime.listType: List.from(runtimes ?? []), - SentryApp.type: app, - SentryBrowser.type: browser, - SentryGpu.type: gpu, - SentryCulture.type: culture, - SentryTraceContext.type: trace, - SentryResponse.type: response, - SentryFeedback.type: feedback, - SentryFeatureFlags.type: flags, - }); - - /// Deserializes [Contexts] from JSON [Map]. - factory Contexts.fromJson(Map data) { - final contexts = Contexts( - device: data[SentryDevice.type] != null - ? SentryDevice.fromJson(Map.from(data[SentryDevice.type])) - : null, - operatingSystem: data[SentryOperatingSystem.type] != null - ? SentryOperatingSystem.fromJson( - Map.from(data[SentryOperatingSystem.type])) - : null, - app: data[SentryApp.type] != null - ? SentryApp.fromJson(Map.from(data[SentryApp.type])) - : null, - browser: data[SentryBrowser.type] != null - ? SentryBrowser.fromJson(Map.from(data[SentryBrowser.type])) - : null, - culture: data[SentryCulture.type] != null - ? SentryCulture.fromJson(Map.from(data[SentryCulture.type])) - : null, - gpu: data[SentryGpu.type] != null - ? SentryGpu.fromJson(Map.from(data[SentryGpu.type])) - : null, - trace: data[SentryTraceContext.type] != null - ? SentryTraceContext.fromJson(Map.from(data[SentryTraceContext.type])) - : null, - runtimes: data[SentryRuntime.type] != null - ? [SentryRuntime.fromJson(Map.from(data[SentryRuntime.type]))] - : null, - response: data[SentryResponse.type] != null - ? SentryResponse.fromJson(Map.from(data[SentryResponse.type])) - : null, - feedback: data[SentryFeedback.type] != null - ? SentryFeedback.fromJson(Map.from(data[SentryFeedback.type])) - : null, - flags: data[SentryFeatureFlags.type] != null - ? SentryFeatureFlags.fromJson(Map.from(data[SentryFeatureFlags.type])) - : null, - ); - - data.keys - .where((key) => !defaultFields.contains(key) && data[key] != null) - .forEach((key) => contexts[key] = data[key]); - - return contexts; - } - - /// This describes the device that caused the event. - SentryDevice? get device => this[SentryDevice.type]; - - set device(SentryDevice? device) => this[SentryDevice.type] = device; - - /// Describes the operating system on which the event was created. - /// - /// In web contexts, this is the operating system of the browse - /// (normally pulled from the User-Agent string). - SentryOperatingSystem? get operatingSystem => - this[SentryOperatingSystem.type]; - - set operatingSystem(SentryOperatingSystem? operatingSystem) => - this[SentryOperatingSystem.type] = operatingSystem; - - /// Describes an immutable list of runtimes in more detail - /// (for instance if you have a Flutter application running - /// on top of Android). - List get runtimes => - List.unmodifiable(this[SentryRuntime.listType] ?? []); - - set runtimes(List runtimes) => - this[SentryRuntime.listType] = List.from(runtimes); - - void addRuntime(SentryRuntime runtime) => - this[SentryRuntime.listType].add(runtime); - - void removeRuntime(SentryRuntime runtime) => - this[SentryRuntime.listType].remove(runtime); - - /// App context describes the application. - /// - /// As opposed to the runtime, this is the actual application that was - /// running and carries metadata about the current session. - SentryApp? get app => this[SentryApp.type]; - - set app(SentryApp? app) => this[SentryApp.type] = app; - - /// Carries information about the browser or user agent for web-related - /// errors. - /// - /// This can either be the browser this event ocurred in, or the user - /// agent of a web request that triggered the event. - SentryBrowser? get browser => this[SentryBrowser.type]; - - set browser(SentryBrowser? browser) => this[SentryBrowser.type] = browser; - - /// Culture Context describes certain properties of the culture in which the - /// software is used. - SentryCulture? get culture => this[SentryCulture.type]; - - set culture(SentryCulture? culture) => this[SentryCulture.type] = culture; - - /// GPU context describes the GPU of the device. - SentryGpu? get gpu => this[SentryGpu.type]; - - set gpu(SentryGpu? gpu) => this[SentryGpu.type] = gpu; - - /// The tracing context of the transaction - SentryTraceContext? get trace => this[SentryTraceContext.type]; - - set trace(SentryTraceContext? trace) => this[SentryTraceContext.type] = trace; - - /// Response context for a HTTP response. Not added automatically. - SentryResponse? get response => this[SentryResponse.type]; - - /// Use [Hint.response] in `beforeSend/beforeSendTransaction` to populate this value. - set response(SentryResponse? value) => this[SentryResponse.type] = value; - - /// Feedback context for a feedback event. - SentryFeedback? get feedback => this[SentryFeedback.type]; - - set feedback(SentryFeedback? value) => this[SentryFeedback.type] = value; - - /// Feature flags context for a feature flag event. - SentryFeatureFlags? get flags => this[SentryFeatureFlags.type]; - - set flags(SentryFeatureFlags? value) => this[SentryFeatureFlags.type] = value; - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - final json = {}; - - forEach((key, value) { - if (value == null) return; - switch (key) { - case SentryDevice.type: - final deviceMap = device?.toJson(); - if (deviceMap?.isNotEmpty ?? false) { - json[SentryDevice.type] = deviceMap; - } - break; - case SentryOperatingSystem.type: - final osMap = operatingSystem?.toJson(); - if (osMap?.isNotEmpty ?? false) { - json[SentryOperatingSystem.type] = osMap; - } - break; - - case SentryApp.type: - final appMap = app?.toJson(); - if (appMap?.isNotEmpty ?? false) { - json[SentryApp.type] = appMap; - } - break; - - case SentryBrowser.type: - final browserMap = browser?.toJson(); - if (browserMap?.isNotEmpty ?? false) { - json[SentryBrowser.type] = browserMap; - } - break; - - case SentryCulture.type: - final cultureMap = culture?.toJson(); - if (cultureMap?.isNotEmpty ?? false) { - json[SentryCulture.type] = cultureMap; - } - break; - - case SentryGpu.type: - final gpuMap = gpu?.toJson(); - if (gpuMap?.isNotEmpty ?? false) { - json[SentryGpu.type] = gpuMap; - } - break; - - case SentryResponse.type: - final responseMap = response?.toJson(); - if (responseMap?.isNotEmpty ?? false) { - json[SentryResponse.type] = responseMap; - } - break; - - case SentryTraceContext.type: - final traceMap = trace?.toJson(); - if (traceMap?.isNotEmpty ?? false) { - json[SentryTraceContext.type] = traceMap; - } - break; - - case SentryFeedback.type: - final feedbackMap = feedback?.toJson(); - if (feedbackMap?.isNotEmpty ?? false) { - json[SentryFeedback.type] = feedbackMap; - } - break; - - case SentryRuntime.listType: - if (runtimes.length == 1) { - final runtime = runtimes[0]; - final runtimeMap = runtime.toJson(); - if (runtimeMap.isNotEmpty) { - final key = runtime.key ?? SentryRuntime.type; - - json[key] = runtimeMap; - } - } else if (runtimes.length > 1) { - for (final runtime in runtimes) { - final runtimeMap = runtime.toJson(); - if (runtimeMap.isNotEmpty) { - var key = runtime.key ?? runtime.name!.toLowerCase(); - - if (json.containsKey(key)) { - var k = 0; - while (json.containsKey(key)) { - key = '$key$k'; - k++; - } - } - json[key] = runtimeMap - ..addAll({'type': SentryRuntime.type}); - } - } - } - - break; - - case SentryFeatureFlags.type: - final flagsMap = flags?.toJson(); - if (flagsMap?.isNotEmpty ?? false) { - json[SentryFeatureFlags.type] = flagsMap; - } - break; - - default: - if (value != null) { - json[key] = value; - } - } - }); - - return json; - } - - @Deprecated('Will be removed in a future version.') - Contexts clone() { - final copy = Contexts( - device: device?.clone(), - operatingSystem: operatingSystem?.clone(), - app: app?.clone(), - browser: browser?.clone(), - culture: culture?.clone(), - gpu: gpu?.clone(), - trace: trace?.clone(), - response: response?.clone(), - runtimes: runtimes.map((runtime) => runtime.clone()).toList(), - feedback: feedback?.clone(), - flags: flags?.clone(), - )..addEntries( - entries.where((element) => !defaultFields.contains(element.key)), - ); - - return copy; - } - - @Deprecated( - 'Will be removed in a future version. Assign values directly to the instance.') - Contexts copyWith({ - SentryDevice? device, - SentryOperatingSystem? operatingSystem, - List? runtimes, - SentryApp? app, - SentryBrowser? browser, - SentryCulture? culture, - SentryGpu? gpu, - SentryTraceContext? trace, - SentryResponse? response, - SentryFeedback? feedback, - SentryFeatureFlags? flags, - }) => - Contexts( - device: device ?? this.device, - operatingSystem: operatingSystem ?? this.operatingSystem, - runtimes: runtimes ?? - List.from(this[SentryRuntime.listType] ?? []), - app: app ?? this.app, - browser: browser ?? this.browser, - gpu: gpu ?? this.gpu, - culture: culture ?? this.culture, - trace: trace ?? this.trace, - response: response ?? this.response, - feedback: feedback ?? this.feedback, - flags: flags ?? this.flags, - )..addEntries( - entries.where((element) => !defaultFields.contains(element.key)), - ); - - @internal - static const defaultFields = [ - SentryApp.type, - SentryDevice.type, - SentryOperatingSystem.type, - SentryRuntime.listType, - SentryRuntime.type, - SentryGpu.type, - SentryBrowser.type, - SentryCulture.type, - SentryTraceContext.type, - SentryResponse.type, - SentryFeedback.type, - SentryFeatureFlags.type, - ]; -} diff --git a/dart/lib/src/protocol/debug_image.dart b/dart/lib/src/protocol/debug_image.dart deleted file mode 100644 index 0791b28099..0000000000 --- a/dart/lib/src/protocol/debug_image.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -/// The list of debug images contains all dynamic libraries loaded into -/// the process and their memory addresses. -/// Instruction addresses in the Stack Trace are mapped into the list of debug -/// images in order to retrieve debug files for symbolication. -/// There are two kinds of debug images: -// -/// Native debug images with types macho, elf, and pe -/// Android debug images with type proguard -/// more details : https://develop.sentry.dev/sdk/event-payloads/debugmeta/ -class DebugImage { - String? uuid; - - /// Required. Type of the debug image. - String type; - - // Name of the image. Sentry-cocoa only. - String? name; - - /// Required. Identifier of the dynamic library or executable. It is the value of the LC_UUID load command in the Mach header, formatted as UUID. - String? debugId; - - /// Required. Memory address, at which the image is mounted in the virtual address space of the process. - /// Should be a string in hex representation prefixed with "0x". - String? imageAddr; - - /// Optional. Preferred load address of the image in virtual memory, as declared in the headers of the image. - /// When loading an image, the operating system may still choose to place it at a different address. - String? imageVmAddr; - - /// Required. The size of the image in virtual memory. If missing, Sentry will assume that the image spans up to the next image, which might lead to invalid stack traces. - int? imageSize; - - /// OptionalName or absolute path to the dSYM file containing debug information for this image. This value might be required to retrieve debug files from certain symbol servers. - String? debugFile; - - /// Optional. The absolute path to the dynamic library or executable. This helps to locate the file if it is missing on Sentry. - String? codeFile; - - /// Optional Architecture of the module. If missing, this will be backfilled by Sentry. - String? arch; - - /// Optional. Identifier of the dynamic library or executable. It is the value of the LC_UUID load command in the Mach header, formatted as UUID. Can be empty for Mach images, as it is equivalent to the debug identifier. - String? codeId; - - /// MachO CPU subtype identifier. - int? cpuSubtype; - - /// MachO CPU type identifier. - int? cpuType; - - @internal - final Map? unknown; - - DebugImage({ - required this.type, - this.name, - this.imageAddr, - this.imageVmAddr, - this.debugId, - this.debugFile, - this.imageSize, - this.uuid, - this.codeFile, - this.arch, - this.codeId, - this.cpuType, - this.cpuSubtype, - this.unknown, - }); - - /// Deserializes a [DebugImage] from JSON [Map]. - factory DebugImage.fromJson(Map data) { - final json = AccessAwareMap(data); - return DebugImage( - type: json['type'], - name: json['name'], - imageAddr: json['image_addr'], - imageVmAddr: json['image_vmaddr'], - debugId: json['debug_id'], - debugFile: json['debug_file'], - imageSize: json['image_size'], - uuid: json['uuid'], - codeFile: json['code_file'], - arch: json['arch'], - codeId: json['code_id'], - cpuType: json['cpu_type'], - cpuSubtype: json['cpu_subtype'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - 'type': type, - if (uuid != null) 'uuid': uuid, - if (debugId != null) 'debug_id': debugId, - if (name != null) 'name': name, - if (debugFile != null) 'debug_file': debugFile, - if (codeFile != null) 'code_file': codeFile, - if (imageAddr != null) 'image_addr': imageAddr, - if (imageVmAddr != null) 'image_vmaddr': imageVmAddr, - if (imageSize != null) 'image_size': imageSize, - if (arch != null) 'arch': arch, - if (codeId != null) 'code_id': codeId, - if (cpuType != null) 'cpu_type': cpuType, - if (cpuSubtype != null) 'cpu_subtype': cpuSubtype, - }; - } - - @Deprecated('Assign values directly to the instance.') - DebugImage copyWith({ - String? uuid, - String? name, - String? type, - String? debugId, - String? debugFile, - String? codeFile, - String? imageAddr, - String? imageVmAddr, - int? imageSize, - String? arch, - String? codeId, - int? cpuType, - int? cpuSubtype, - }) => - DebugImage( - uuid: uuid ?? this.uuid, - name: name ?? this.name, - type: type ?? this.type, - debugId: debugId ?? this.debugId, - debugFile: debugFile ?? this.debugFile, - codeFile: codeFile ?? this.codeFile, - imageAddr: imageAddr ?? this.imageAddr, - imageVmAddr: imageVmAddr ?? this.imageVmAddr, - imageSize: imageSize ?? this.imageSize, - arch: arch ?? this.arch, - codeId: codeId ?? this.codeId, - cpuType: cpuType ?? this.cpuType, - cpuSubtype: cpuSubtype ?? this.cpuSubtype, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/debug_meta.dart b/dart/lib/src/protocol/debug_meta.dart deleted file mode 100644 index b82164300a..0000000000 --- a/dart/lib/src/protocol/debug_meta.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../protocol.dart'; -import 'access_aware_map.dart'; - -/// The debug meta interface carries debug information for processing errors and crash reports. -class DebugMeta { - /// An object describing the system SDK. - SdkInfo? sdk; - - List? _images; - - /// The immutable list of debug images contains all dynamic libraries loaded - /// into the process and their memory addresses. - /// Instruction addresses in the Stack Trace are mapped into the list of debug - /// images in order to retrieve debug files for symbolication. - List get images => List.unmodifiable(_images ?? const []); - - void addDebugImage(DebugImage debugImage) { - _images ??= []; - _images?.add(debugImage); - } - - DebugMeta({this.sdk, List? images, this.unknown}) - : _images = images; - - @internal - final Map? unknown; - - /// Deserializes a [DebugMeta] from JSON [Map]. - factory DebugMeta.fromJson(Map data) { - final json = AccessAwareMap(data); - final sdkInfoJson = json['sdk_info']; - final debugImagesJson = json['images'] as List?; - return DebugMeta( - sdk: sdkInfoJson != null ? SdkInfo.fromJson(sdkInfoJson) : null, - images: debugImagesJson - ?.map((debugImageJson) => - DebugImage.fromJson(debugImageJson as Map)) - .toList(), - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - final sdkInfo = sdk?.toJson(); - return { - ...?unknown, - if (sdkInfo?.isNotEmpty ?? false) 'sdk_info': sdkInfo, - if (_images?.isNotEmpty ?? false) - 'images': _images! - .map((e) => e.toJson()) - .where((element) => element.isNotEmpty) - .toList(growable: false), - }; - } - - @Deprecated('Assign values directly to the instance.') - DebugMeta copyWith({ - SdkInfo? sdk, - List? images, - }) => - DebugMeta( - sdk: sdk ?? this.sdk, - images: images ?? _images, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/dsn.dart b/dart/lib/src/protocol/dsn.dart deleted file mode 100644 index c3ec5093c8..0000000000 --- a/dart/lib/src/protocol/dsn.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:meta/meta.dart'; - -/// The Data Source Name (DSN) tells the SDK where to send the events -@immutable -class Dsn { - const Dsn({ - required this.publicKey, - required this.projectId, - this.uri, - this.secretKey, - }); - - /// The Sentry.io public key for the project. - final String publicKey; - - /// The Sentry.io secret key for the project. - final String? secretKey; - - /// The ID issued by Sentry.io to your project. - /// - /// Attached to the event payload. - final String projectId; - - /// The DSN URI. - final Uri? uri; - - Uri get postUri { - final uriCopy = uri!; - final port = uriCopy.hasPort && - ((uriCopy.scheme == 'http' && uriCopy.port != 80) || - (uriCopy.scheme == 'https' && uriCopy.port != 443)) - ? ':${uriCopy.port}' - : ''; - - final pathLength = uriCopy.pathSegments.length; - - String apiPath; - if (pathLength > 1) { - // some paths would present before the projectID in the uri - apiPath = - (uriCopy.pathSegments.sublist(0, pathLength - 1) + ['api']).join('/'); - } else { - apiPath = 'api'; - } - return Uri.parse( - '${uriCopy.scheme}://${uriCopy.host}$port/$apiPath/$projectId/envelope/', - ); - } - - /// Parses a DSN String to a Dsn object - factory Dsn.parse(String dsn) { - final uri = Uri.parse(dsn); - final userInfo = uri.userInfo.split(':'); - - if (uri.pathSegments.isEmpty) { - throw ArgumentError( - 'Project ID not found in the URI path of the DSN URI: $dsn', - ); - } - - return Dsn( - publicKey: userInfo[0], - secretKey: userInfo.length >= 2 ? userInfo[1] : null, - projectId: uri.pathSegments.last, - uri: uri, - ); - } -} diff --git a/dart/lib/src/protocol/max_body_size.dart b/dart/lib/src/protocol/max_body_size.dart deleted file mode 100644 index 448b021975..0000000000 --- a/dart/lib/src/protocol/max_body_size.dart +++ /dev/null @@ -1,73 +0,0 @@ -// See https://docs.sentry.io/platforms/dotnet/guides/aspnetcore/configuration/options/#max-request-body-size - -const _mediumSize = 10000; -const _smallSize = 4000; - -/// Describes the size of http request bodies which should be added to an event -enum MaxRequestBodySize { - /// Request bodies are never sent - never, - - /// Only small request bodies will be captured where the cutoff for small - /// depends on the SDK (typically 4KB) - small, - - /// Medium and small requests will be captured (typically 10KB) - medium, - - /// The SDK will always capture the request body for as long as Sentry can - /// make sense of it - always, -} - -extension MaxRequestBodySizeX on MaxRequestBodySize { - bool shouldAddBody(int contentLength) { - switch (this) { - case MaxRequestBodySize.never: - break; - case MaxRequestBodySize.small: - return contentLength <= _smallSize; - case MaxRequestBodySize.medium: - return contentLength <= _mediumSize; - case MaxRequestBodySize.always: - return true; - // No default here to get a warning when a new enum value is added. - } - return false; - } -} - -/// Describes the size of http response bodies which should be added to an event -/// This enum might be removed at any time. -enum MaxResponseBodySize { - /// Response bodies are never sent - never, - - /// Only small response bodies will be captured where the cutoff for small - /// depends on the SDK (typically 4KB) - small, - - /// Medium and small response will be captured (typically 10KB) - medium, - - /// The SDK will always capture the request body for as long as Sentry can - /// make sense of it - always, -} - -extension MaxResponseBodySizeX on MaxResponseBodySize { - bool shouldAddBody(int contentLength) { - switch (this) { - case MaxResponseBodySize.never: - break; - case MaxResponseBodySize.small: - return contentLength <= _smallSize; - case MaxResponseBodySize.medium: - return contentLength <= _mediumSize; - case MaxResponseBodySize.always: - return true; - // No default here to get a warning when a new enum value is added. - } - return false; - } -} diff --git a/dart/lib/src/protocol/mechanism.dart b/dart/lib/src/protocol/mechanism.dart deleted file mode 100644 index 0ee37f7d16..0000000000 --- a/dart/lib/src/protocol/mechanism.dart +++ /dev/null @@ -1,178 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -/// Sentry Exception Mechanism -/// The exception mechanism is an optional field residing -/// in the Exception Interface. It carries additional information about -/// the way the exception was created on the target system. -/// This includes general exception values obtained from operating system or -/// runtime APIs, as well as mechanism-specific values. -class Mechanism { - /// Required unique identifier of this mechanism determining rendering and processing of the mechanism data - /// The type attribute is required to send any exception mechanism attribute, - /// even if the SDK cannot determine the specific mechanism. - /// In this case, set the type to "generic". See below for an example. - String type; - - /// Optional human readable description of the error mechanism and a possible hint on how to solve this error - String? description; - - /// Optional fully qualified URL to an online help resource, possible interpolated with error parameters - String? helpLink; - - /// Optional flag indicating whether the exception has been handled by the user (e.g. via try..catch) - bool? handled; - - Map? _meta; - - /// Optional information from the operating system or runtime on the exception mechanism - /// The mechanism meta data usually carries error codes reported by - /// the runtime or operating system, along with a platform dependent - /// interpretation of these codes. SDKs can safely omit code names and - /// descriptions for well known error codes, as it will be filled out by - /// Sentry. For proprietary or vendor-specific error codes, - /// adding these values will give additional information to the user. - Map get meta => Map.unmodifiable(_meta ?? const {}); - - Map? _data; - - /// Arbitrary extra data that might help the user understand the error thrown by this mechanism - Map get data => Map.unmodifiable(_data ?? const {}); - - set data(Map? data) { - _data = (data != null) ? Map.from(data) : null; - } - - /// An optional flag indicating that this error is synthetic. - /// Synthetic errors are errors that carry little meaning by themselves. - /// This may be because they are created at a central place (like a crash handler), and are all called the same: Error, Segfault etc. When the flag is set, Sentry will then try to use other information (top in-app frame function) rather than exception type and value in the UI for the primary event display. This flag should be set for all "segfaults" for instance as every single error group would look very similar otherwise. - bool? synthetic; - - /// An optional boolean value, set `true` when the exception is the exception - /// group type specific to the platform or language. - /// The default is false when omitted. - /// For example, exceptions of type [PlatformException](https://api.flutter.dev/flutter/services/PlatformException-class.html) - /// have set it to `true`, others are set to `false`. - bool? isExceptionGroup; - - /// An optional string value describing the source of the exception. - /// - /// The SDK should populate this with the name of the property or attribute of - /// the parent exception that this exception was acquired from. - /// In the case of an array, it should include the zero-based array index as - /// well. - String? source; - - /// An optional numeric value providing an ID for the exception relative to - /// this specific event. - /// - /// The SDK should assign simple incrementing integers to each exception in - /// the tree, starting with 0 for the root of the tree. - /// In other words, when flattened into the list provided in the exception - /// values on the event, the last exception in the list should have ID 0, - /// the previous one should have ID 1, the next previous should have ID 2, etc. - int? exceptionId; - - /// An optional numeric value pointing at the [exceptionId] that is the parent - /// of this exception. - /// - /// The SDK should assign this to all exceptions except the root exception - /// (the last to be listed in the exception values). - int? parentId; - - @internal - final Map? unknown; - - Mechanism({ - required this.type, - this.description, - this.helpLink, - this.handled, - this.synthetic, - Map? meta, - Map? data, - this.isExceptionGroup, - this.source, - this.exceptionId, - this.parentId, - this.unknown, - }) : _meta = meta != null ? Map.from(meta) : null, - _data = data != null ? Map.from(data) : null; - - @Deprecated('Assign values directly to the instance.') - Mechanism copyWith({ - String? type, - String? description, - String? helpLink, - bool? handled, - Map? meta, - Map? data, - bool? synthetic, - bool? isExceptionGroup, - String? source, - int? exceptionId, - int? parentId, - }) => - Mechanism( - type: type ?? this.type, - description: description ?? this.description, - helpLink: helpLink ?? this.helpLink, - handled: handled ?? this.handled, - meta: meta ?? this.meta, - data: data ?? this.data, - synthetic: synthetic ?? this.synthetic, - isExceptionGroup: isExceptionGroup ?? this.isExceptionGroup, - source: source ?? this.source, - exceptionId: exceptionId ?? this.exceptionId, - parentId: parentId ?? this.parentId, - unknown: unknown, - ); - - /// Deserializes a [Mechanism] from JSON [Map]. - factory Mechanism.fromJson(Map jsonData) { - final json = AccessAwareMap(jsonData); - var data = json['data']; - if (data != null) { - data = Map.from(data as Map); - } - - var meta = json['meta']; - if (meta != null) { - meta = Map.from(meta as Map); - } - - return Mechanism( - type: json['type'], - description: json['description'], - helpLink: json['help_link'], - handled: json['handled'], - meta: meta, - data: data, - synthetic: json['synthetic'], - isExceptionGroup: json['is_exception_group'], - source: json['source'], - exceptionId: json['exception_id'], - parentId: json['parent_id'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - 'type': type, - if (description != null) 'description': description, - if (helpLink != null) 'help_link': helpLink, - if (handled != null) 'handled': handled, - if (_meta?.isNotEmpty ?? false) 'meta': _meta, - if (_data?.isNotEmpty ?? false) 'data': _data, - if (synthetic != null) 'synthetic': synthetic, - if (isExceptionGroup != null) 'is_exception_group': isExceptionGroup, - if (source != null) 'source': source, - if (exceptionId != null) 'exception_id': exceptionId, - if (parentId != null) 'parent_id': parentId, - }; - } -} diff --git a/dart/lib/src/protocol/sdk_info.dart b/dart/lib/src/protocol/sdk_info.dart deleted file mode 100644 index 7903f910d3..0000000000 --- a/dart/lib/src/protocol/sdk_info.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -/// An object describing the system SDK. -class SdkInfo { - String? sdkName; - int? versionMajor; - int? versionMinor; - int? versionPatchlevel; - - @internal - final Map? unknown; - - SdkInfo({ - this.sdkName, - this.versionMajor, - this.versionMinor, - this.versionPatchlevel, - this.unknown, - }); - - /// Deserializes a [SdkInfo] from JSON [Map]. - factory SdkInfo.fromJson(Map data) { - final json = AccessAwareMap(data); - return SdkInfo( - sdkName: json['sdk_name'], - versionMajor: json['version_major'], - versionMinor: json['version_minor'], - versionPatchlevel: json['version_patchlevel'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - if (sdkName != null) 'sdk_name': sdkName, - if (versionMajor != null) 'version_major': versionMajor, - if (versionMinor != null) 'version_minor': versionMinor, - if (versionPatchlevel != null) 'version_patchlevel': versionPatchlevel, - }; - } - - @Deprecated('Assign values directly to the instance.') - SdkInfo copyWith({ - String? sdkName, - int? versionMajor, - int? versionMinor, - int? versionPatchlevel, - }) => - SdkInfo( - sdkName: sdkName ?? this.sdkName, - versionMajor: versionMajor ?? this.versionMajor, - versionMinor: versionMinor ?? this.versionMinor, - versionPatchlevel: versionPatchlevel ?? this.versionPatchlevel, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/sdk_version.dart b/dart/lib/src/protocol/sdk_version.dart deleted file mode 100644 index b80483accd..0000000000 --- a/dart/lib/src/protocol/sdk_version.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'sentry_package.dart'; -import 'access_aware_map.dart'; - -/// Describes the SDK that is submitting events to Sentry. -/// -/// https://develop.sentry.dev/sdk/event-payloads/sdk/ -/// -/// SDK's maintained by Sentry take the following format: -/// sentry.lang and for specializations: sentry.lang.specialization -/// -/// Examples: sentry.dart, sentry.dart.browser, sentry.dart.flutter -/// -/// It can also contain the packages bundled and integrations enabled. -/// -/// ``` -/// "sdk": { -/// "name": "sentry.dart.flutter", -/// "version": "5.0.0", -/// "integrations": [ -/// "tracing" -/// ], -/// "packages": [ -/// { -/// "name": "git:https://github.com/getsentry/sentry-cocoa.git", -/// "version": "5.1.0" -/// }, -/// { -/// "name": "maven:io.sentry.android", -/// "version": "2.2.0" -/// } -/// ] -/// } -/// ``` -class SdkVersion { - /// Creates an [SdkVersion] object which represents the SDK that created an [Event]. - SdkVersion({ - required this.name, - required this.version, - List? integrations, - List? packages, - this.unknown, - }) : - // List.from prevents from having immutable lists - _integrations = List.from(integrations ?? []), - _packages = List.from(packages ?? []); - - /// The name of the SDK. - String name; - - /// The version of the SDK. - String version; - - List _integrations; - - /// An immutable list of integrations enabled in the SDK that created the [Event]. - List get integrations => List.unmodifiable(_integrations); - - List _packages; - - /// An immutable list of packages that compose this SDK. - List get packages => List.unmodifiable(_packages); - - @internal - final Map? unknown; - - /// Deserializes a [SdkVersion] from JSON [Map]. - factory SdkVersion.fromJson(Map data) { - final json = AccessAwareMap(data); - final packagesJson = json['packages'] as List?; - final integrationsJson = json['integrations'] as List?; - - return SdkVersion( - name: json['name'], - version: json['version'], - packages: packagesJson - ?.map((e) => SentryPackage.fromJson(e as Map)) - .toList(), - integrations: integrationsJson?.map((e) => e as String).toList(), - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - 'name': name, - 'version': version, - if (packages.isNotEmpty) - 'packages': packages.map((p) => p.toJson()).toList(growable: false), - if (integrations.isNotEmpty) 'integrations': integrations, - }; - } - - /// Adds a package - void addPackage(String name, String version) { - for (final item in _packages) { - if (item.name == name && item.version == version) { - return; - } - } - - final package = SentryPackage(name, version); - _packages.add(package); - } - - // Adds an integration if not already added - void addIntegration(String integration) { - if (_integrations.contains(integration)) { - return; - } - _integrations.add(integration); - } - - @Deprecated('Assign values directly to the instance.') - SdkVersion copyWith({ - String? name, - String? version, - List? integrations, - List? packages, - }) => - SdkVersion( - name: name ?? this.name, - version: version ?? this.version, - integrations: integrations ?? _integrations, - packages: packages ?? _packages, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/sentry_app.dart b/dart/lib/src/protocol/sentry_app.dart deleted file mode 100644 index 6495ff38a0..0000000000 --- a/dart/lib/src/protocol/sentry_app.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -/// App context describes the application. -/// -/// As opposed to the runtime, this is the actual application that was -/// running and carries metadata about the current session. -class SentryApp { - static const type = 'app'; - - SentryApp({ - this.name, - this.version, - this.identifier, - this.build, - this.buildType, - this.startTime, - this.deviceAppHash, - this.appMemory, - this.inForeground, - this.viewNames, - this.textScale, - this.unknown, - }); - - /// Human readable application name, as it appears on the platform. - String? name; - - /// Human readable application version, as it appears on the platform. - String? version; - - /// Version-independent application identifier, often a dotted bundle ID. - String? identifier; - - /// Internal build identifier, as it appears on the platform. - String? build; - - /// String identifying the kind of build, e.g. `testflight`. - String? buildType; - - /// When the application was started by the user. - DateTime? startTime; - - /// Application specific device identifier. - String? deviceAppHash; - - /// Amount of memory used by the application in bytes. - int? appMemory; - - /// A flag indicating whether the app is in foreground or not. - /// An app is in foreground when it's visible to the user. - bool? inForeground; - - /// The names of the currently visible views. - List? viewNames; - - /// The current text scale. Only available on Flutter. - double? textScale; - - @internal - final Map? unknown; - - /// Deserializes a [SentryApp] from JSON [Map]. - factory SentryApp.fromJson(Map data) { - final json = AccessAwareMap(data); - final viewNamesJson = json['view_names'] as List?; - return SentryApp( - name: json['app_name'], - version: json['app_version'], - identifier: json['app_identifier'], - build: json['app_build'], - buildType: json['build_type'], - startTime: json['app_start_time'] != null - ? DateTime.tryParse(json['app_start_time']) - : null, - deviceAppHash: json['device_app_hash'], - appMemory: json['app_memory'], - inForeground: json['in_foreground'], - viewNames: viewNamesJson?.map((e) => e as String).toList(), - textScale: json['text_scale'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - if (name != null) 'app_name': name!, - if (version != null) 'app_version': version!, - if (identifier != null) 'app_identifier': identifier!, - if (build != null) 'app_build': build!, - if (buildType != null) 'build_type': buildType!, - if (startTime != null) 'app_start_time': startTime!.toIso8601String(), - if (deviceAppHash != null) 'device_app_hash': deviceAppHash!, - if (appMemory != null) 'app_memory': appMemory!, - if (inForeground != null) 'in_foreground': inForeground!, - if (viewNames != null && viewNames!.isNotEmpty) 'view_names': viewNames!, - if (textScale != null) 'text_scale': textScale!, - }; - } - - @Deprecated('Will be removed in a future version.') - SentryApp clone() => SentryApp( - name: name, - version: version, - identifier: identifier, - build: build, - buildType: buildType, - startTime: startTime, - deviceAppHash: deviceAppHash, - appMemory: appMemory, - inForeground: inForeground, - viewNames: viewNames, - textScale: textScale, - unknown: unknown, - ); - - @Deprecated('Assign values directly to the instance.') - SentryApp copyWith({ - String? name, - String? version, - String? identifier, - String? build, - String? buildType, - DateTime? startTime, - String? deviceAppHash, - int? appMemory, - bool? inForeground, - List? viewNames, - double? textScale, - }) => - SentryApp( - name: name ?? this.name, - version: version ?? this.version, - identifier: identifier ?? this.identifier, - build: build ?? this.build, - buildType: buildType ?? this.buildType, - startTime: startTime ?? this.startTime, - deviceAppHash: deviceAppHash ?? this.deviceAppHash, - appMemory: appMemory ?? this.appMemory, - inForeground: inForeground ?? this.inForeground, - viewNames: viewNames ?? this.viewNames, - textScale: textScale ?? this.textScale, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/sentry_baggage_header.dart b/dart/lib/src/protocol/sentry_baggage_header.dart deleted file mode 100644 index 3d7007030a..0000000000 --- a/dart/lib/src/protocol/sentry_baggage_header.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../sentry_baggage.dart'; - -@immutable -class SentryBaggageHeader { - static const _traceHeader = 'baggage'; - - SentryBaggageHeader(this.value); - - final String value; - - String get name => _traceHeader; - - factory SentryBaggageHeader.fromBaggage(SentryBaggage baggage) { - return SentryBaggageHeader(baggage.toHeaderString()); - } -} diff --git a/dart/lib/src/protocol/sentry_browser.dart b/dart/lib/src/protocol/sentry_browser.dart deleted file mode 100644 index 9ba214dac9..0000000000 --- a/dart/lib/src/protocol/sentry_browser.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -/// Carries information about the browser or user agent for web-related errors. -/// -/// This can either be the browser this event ocurred in, or the user -/// agent of a web request that triggered the event. -class SentryBrowser { - static const type = 'browser'; - - /// Creates an instance of [SentryBrowser]. - SentryBrowser({this.name, this.version, this.unknown}); - - /// Human readable application name, as it appears on the platform. - String? name; - - /// Human readable application version, as it appears on the platform. - String? version; - - @internal - final Map? unknown; - - /// Deserializes a [SentryBrowser] from JSON [Map]. - factory SentryBrowser.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryBrowser( - name: json['name'], - version: json['version'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - if (name != null) 'name': name, - if (version != null) 'version': version, - }; - } - - @Deprecated('Will be removed in a future version.') - SentryBrowser clone() => SentryBrowser( - name: name, - version: version, - unknown: unknown, - ); - - @Deprecated('Assign values directly to the instance.') - SentryBrowser copyWith({ - String? name, - String? version, - }) => - SentryBrowser( - name: name ?? this.name, - version: version ?? this.version, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/sentry_culture.dart b/dart/lib/src/protocol/sentry_culture.dart deleted file mode 100644 index 300f697afc..0000000000 --- a/dart/lib/src/protocol/sentry_culture.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -/// Culture Context describes certain properties of the culture in which the -/// software is used. -class SentryCulture { - static const type = 'culture'; - - SentryCulture({ - this.calendar, - this.displayName, - this.locale, - this.is24HourFormat, - this.timezone, - this.unknown, - }); - - factory SentryCulture.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryCulture( - calendar: json['calendar'], - displayName: json['display_name'], - locale: json['locale'], - is24HourFormat: json['is_24_hour_format'], - timezone: json['timezone'], - unknown: json.notAccessed(), - ); - } - - /// Optional: For example `GregorianCalendar`. Free form string. - String? calendar; - - /// Optional: Human readable name of the culture. - /// For example `English (United States)` - String? displayName; - - /// Optional. The name identifier, usually following the RFC 4646. - /// For example `en-US` or `pt-BR`. - String? locale; - - /// Optional. boolean, either true or false. - bool? is24HourFormat; - - /// Optional. The timezone of the locale. For example, `Europe/Vienna`. - String? timezone; - - @internal - final Map? unknown; - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - if (calendar != null) 'calendar': calendar!, - if (displayName != null) 'display_name': displayName!, - if (locale != null) 'locale': locale!, - if (is24HourFormat != null) 'is_24_hour_format': is24HourFormat!, - if (timezone != null) 'timezone': timezone!, - }; - } - - @Deprecated('Will be removed in a future version.') - SentryCulture clone() => SentryCulture( - calendar: calendar, - displayName: displayName, - locale: locale, - is24HourFormat: is24HourFormat, - timezone: timezone, - unknown: unknown, - ); - - @Deprecated('Assign values directly to the instance.') - SentryCulture copyWith({ - String? calendar, - String? displayName, - String? locale, - bool? is24HourFormat, - String? timezone, - }) => - SentryCulture( - calendar: calendar ?? this.calendar, - displayName: displayName ?? this.displayName, - locale: locale ?? this.locale, - is24HourFormat: is24HourFormat ?? this.is24HourFormat, - timezone: timezone ?? this.timezone, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/sentry_device.dart b/dart/lib/src/protocol/sentry_device.dart deleted file mode 100644 index 8af83f61c5..0000000000 --- a/dart/lib/src/protocol/sentry_device.dart +++ /dev/null @@ -1,401 +0,0 @@ -import 'package:meta/meta.dart'; -import '../sentry_options.dart'; -import 'access_aware_map.dart'; - -/// If a device is on portrait or landscape mode -enum SentryOrientation { portrait, landscape } - -/// This describes the device that caused the event. -class SentryDevice { - static const type = 'device'; - - SentryDevice({ - this.name, - this.family, - this.model, - this.modelId, - this.arch, - this.batteryLevel, - this.orientation, - this.manufacturer, - this.brand, - this.screenHeightPixels, - this.screenWidthPixels, - this.screenDensity, - this.screenDpi, - this.online, - this.charging, - this.lowMemory, - this.simulator, - this.memorySize, - this.freeMemory, - this.usableMemory, - this.storageSize, - this.freeStorage, - this.externalStorageSize, - this.externalFreeStorage, - this.bootTime, - this.processorCount, - this.cpuDescription, - this.processorFrequency, - this.deviceType, - this.batteryStatus, - this.deviceUniqueIdentifier, - this.supportsVibration, - this.supportsAccelerometer, - this.supportsGyroscope, - this.supportsAudio, - this.supportsLocationService, - this.unknown, - }) : assert( - batteryLevel == null || (batteryLevel >= 0 && batteryLevel <= 100), - ); - - /// The name of the device. This is typically a hostname. - String? name; - - /// The family of the device. - /// - /// This is normally the common part of model names across generations. - /// For instance `iPhone` would be a reasonable family, - /// so would be `Samsung Galaxy`. - String? family; - - /// The model name. This for instance can be `Samsung Galaxy S3`. - String? model; - - /// An internal hardware revision to identify the device exactly. - String? modelId; - - /// The CPU architecture. - String? arch; - - /// If the device has a battery, this can be an floating point value - /// defining the battery level (in the range 0-100). - double? batteryLevel; - - /// Defines the orientation of a device. - SentryOrientation? orientation; - - /// The manufacturer of the device. - String? manufacturer; - - /// The brand of the device. - String? brand; - - /// The screen height in pixels. (e.g.: `600`, `1080`). - int? screenHeightPixels; - - /// The screen width in pixels. (e.g.: `800`, `1920`). - int? screenWidthPixels; - - /// A floating point denoting the screen density. - double? screenDensity; - - /// A decimal value reflecting the DPI (dots-per-inch) density. - int? screenDpi; - - /// Whether the device was online or not. - bool? online; - - /// Whether the device was charging or not. - bool? charging; - - /// Whether the device was low on memory. - bool? lowMemory; - - /// A flag indicating whether this device is a simulator or an actual device. - bool? simulator; - - /// Total system memory available in bytes. - int? memorySize; - - /// Free system memory in bytes. - int? freeMemory; - - /// Memory usable for the app in bytes. - int? usableMemory; - - /// Total device storage in bytes. - int? storageSize; - - /// Free device storage in bytes. - int? freeStorage; - - /// Total size of an attached external storage in bytes - /// (e.g.: android SDK card). - int? externalStorageSize; - - /// Free size of an attached external storage in bytes - /// (e.g.: android SDK card). - int? externalFreeStorage; - - /// When the system was booted - DateTime? bootTime; - - /// Optional. Number of "logical processors". For example, `8`. - int? processorCount; - - /// Optional. CPU description. For example, `Intel(R) Core(TM)2 Quad CPU Q6600 @ 2.40GHz`. - String? cpuDescription; - - /// Optional. Processor frequency in MHz. Note that the actual CPU frequency - /// might vary depending on current load and power conditions, - /// especially on low-powered devices like phones and laptops. - double? processorFrequency; - - /// Optional. Kind of device the application is running on. - /// For example, `Unknown`, `Handheld`, `Console`, `Desktop`. - String? deviceType; - - /// Optional. Status of the device's battery. - /// For example, `Unknown`, `Charging`, `Discharging`, `NotCharging`, `Full`. - String? batteryStatus; - - /// Optional. Unique device identifier. - /// This value might only be used if [SentryOptions.sendDefaultPii] - /// is enabled. - String? deviceUniqueIdentifier; - - /// Optional. Is vibration available on the device? - bool? supportsVibration; - - /// Optional. Is accelerometer available on the device? - bool? supportsAccelerometer; - - /// Optional. Is gyroscope available on the device? - bool? supportsGyroscope; - - /// Optional. Is audio available on the device? - bool? supportsAudio; - - /// Optional. Is the device capable of reporting its location? - bool? supportsLocationService; - - @internal - final Map? unknown; - - /// Deserializes a [SentryDevice] from JSON [Map]. - factory SentryDevice.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryDevice( - name: json['name'], - family: json['family'], - model: json['model'], - modelId: json['model_id'], - arch: json['arch'], - batteryLevel: - (json['battery_level'] is num ? json['battery_level'] as num : null) - ?.toDouble(), - orientation: json['orientation'] == 'portrait' - ? SentryOrientation.portrait - : json['orientation'] == 'landscape' - ? SentryOrientation.landscape - : null, - manufacturer: json['manufacturer'], - brand: json['brand'], - screenHeightPixels: json['screen_height_pixels']?.toInt(), - screenWidthPixels: json['screen_width_pixels']?.toInt(), - screenDensity: json['screen_density'], - screenDpi: json['screen_dpi'], - online: json['online'], - charging: json['charging'], - lowMemory: json['low_memory'], - simulator: json['simulator'], - memorySize: json['memory_size'], - freeMemory: json['free_memory'], - usableMemory: json['usable_memory'], - storageSize: json['storage_size'], - freeStorage: json['free_storage'], - externalStorageSize: json['external_storage_size'], - externalFreeStorage: json['external_free_storage'], - bootTime: json['boot_time'] != null - ? DateTime.tryParse(json['boot_time']) - : null, - processorCount: json['processor_count'], - cpuDescription: json['cpu_description'], - processorFrequency: json['processor_frequency'], - deviceType: json['device_type'], - batteryStatus: json['battery_status'], - deviceUniqueIdentifier: json['device_unique_identifier'], - supportsVibration: json['supports_vibration'], - supportsAccelerometer: json['supports_accelerometer'], - supportsGyroscope: json['supports_gyroscope'], - supportsAudio: json['supports_audio'], - supportsLocationService: json['supports_location_service'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - if (name != null) 'name': name, - if (family != null) 'family': family, - if (model != null) 'model': model, - if (modelId != null) 'model_id': modelId, - if (arch != null) 'arch': arch, - if (batteryLevel != null) 'battery_level': batteryLevel, - if (orientation != null) 'orientation': orientation!.name, - if (manufacturer != null) 'manufacturer': manufacturer, - if (brand != null) 'brand': brand, - if (screenWidthPixels != null) 'screen_width_pixels': screenWidthPixels, - if (screenHeightPixels != null) - 'screen_height_pixels': screenHeightPixels, - if (screenDensity != null) 'screen_density': screenDensity, - if (screenDpi != null) 'screen_dpi': screenDpi, - if (online != null) 'online': online, - if (charging != null) 'charging': charging, - if (lowMemory != null) 'low_memory': lowMemory, - if (simulator != null) 'simulator': simulator, - if (memorySize != null) 'memory_size': memorySize, - if (freeMemory != null) 'free_memory': freeMemory, - if (usableMemory != null) 'usable_memory': usableMemory, - if (storageSize != null) 'storage_size': storageSize, - if (freeStorage != null) 'free_storage': freeStorage, - if (externalStorageSize != null) - 'external_storage_size': externalStorageSize, - if (externalFreeStorage != null) - 'external_free_storage': externalFreeStorage, - if (bootTime != null) 'boot_time': bootTime!.toIso8601String(), - if (processorCount != null) 'processor_count': processorCount, - if (cpuDescription != null) 'cpu_description': cpuDescription, - if (processorFrequency != null) 'processor_frequency': processorFrequency, - if (deviceType != null) 'device_type': deviceType, - if (batteryStatus != null) 'battery_status': batteryStatus, - if (deviceUniqueIdentifier != null) - 'device_unique_identifier': deviceUniqueIdentifier, - if (supportsVibration != null) 'supports_vibration': supportsVibration, - if (supportsAccelerometer != null) - 'supports_accelerometer': supportsAccelerometer, - if (supportsGyroscope != null) 'supports_gyroscope': supportsGyroscope, - if (supportsAudio != null) 'supports_audio': supportsAudio, - if (supportsLocationService != null) - 'supports_location_service': supportsLocationService, - }; - } - - @Deprecated('Will be removed in a future version.') - SentryDevice clone() => SentryDevice( - name: name, - family: family, - model: model, - modelId: modelId, - arch: arch, - batteryLevel: batteryLevel, - orientation: orientation, - manufacturer: manufacturer, - brand: brand, - screenHeightPixels: screenHeightPixels, - screenWidthPixels: screenWidthPixels, - screenDensity: screenDensity, - screenDpi: screenDpi, - online: online, - charging: charging, - lowMemory: lowMemory, - simulator: simulator, - memorySize: memorySize, - freeMemory: freeMemory, - usableMemory: usableMemory, - storageSize: storageSize, - freeStorage: freeStorage, - externalStorageSize: externalStorageSize, - externalFreeStorage: externalFreeStorage, - bootTime: bootTime, - processorCount: processorCount, - cpuDescription: cpuDescription, - processorFrequency: processorFrequency, - deviceType: deviceType, - batteryStatus: batteryStatus, - deviceUniqueIdentifier: deviceUniqueIdentifier, - supportsVibration: supportsVibration, - supportsAccelerometer: supportsAccelerometer, - supportsGyroscope: supportsGyroscope, - supportsAudio: supportsAudio, - supportsLocationService: supportsLocationService, - unknown: unknown, - ); - - @Deprecated('Assign values directly to the instance.') - SentryDevice copyWith({ - String? name, - String? family, - String? model, - String? modelId, - String? arch, - double? batteryLevel, - SentryOrientation? orientation, - String? manufacturer, - String? brand, - int? screenHeightPixels, - int? screenWidthPixels, - double? screenDensity, - int? screenDpi, - bool? online, - bool? charging, - bool? lowMemory, - bool? simulator, - int? memorySize, - int? freeMemory, - int? usableMemory, - int? storageSize, - int? freeStorage, - int? externalStorageSize, - int? externalFreeStorage, - DateTime? bootTime, - int? processorCount, - String? cpuDescription, - double? processorFrequency, - String? deviceType, - String? batteryStatus, - String? deviceUniqueIdentifier, - bool? supportsVibration, - bool? supportsAccelerometer, - bool? supportsGyroscope, - bool? supportsAudio, - bool? supportsLocationService, - }) => - SentryDevice( - name: name ?? this.name, - family: family ?? this.family, - model: model ?? this.model, - modelId: modelId ?? this.modelId, - arch: arch ?? this.arch, - batteryLevel: batteryLevel ?? this.batteryLevel, - orientation: orientation ?? this.orientation, - manufacturer: manufacturer ?? this.manufacturer, - brand: brand ?? this.brand, - screenHeightPixels: screenHeightPixels ?? this.screenHeightPixels, - screenWidthPixels: screenWidthPixels ?? this.screenWidthPixels, - screenDensity: screenDensity ?? this.screenDensity, - screenDpi: screenDpi ?? this.screenDpi, - online: online ?? this.online, - charging: charging ?? this.charging, - lowMemory: lowMemory ?? this.lowMemory, - simulator: simulator ?? this.simulator, - memorySize: memorySize ?? this.memorySize, - freeMemory: freeMemory ?? this.freeMemory, - usableMemory: usableMemory ?? this.usableMemory, - storageSize: storageSize ?? this.storageSize, - freeStorage: freeStorage ?? this.freeStorage, - externalStorageSize: externalStorageSize ?? this.externalStorageSize, - externalFreeStorage: externalFreeStorage ?? this.externalFreeStorage, - bootTime: bootTime ?? this.bootTime, - processorCount: processorCount ?? this.processorCount, - cpuDescription: cpuDescription ?? this.cpuDescription, - processorFrequency: processorFrequency ?? this.processorFrequency, - deviceType: deviceType ?? this.deviceType, - batteryStatus: batteryStatus ?? this.batteryStatus, - deviceUniqueIdentifier: - deviceUniqueIdentifier ?? this.deviceUniqueIdentifier, - supportsVibration: supportsVibration ?? this.supportsVibration, - supportsAccelerometer: - supportsAccelerometer ?? this.supportsAccelerometer, - supportsGyroscope: supportsGyroscope ?? this.supportsGyroscope, - supportsAudio: supportsAudio ?? this.supportsAudio, - supportsLocationService: - supportsLocationService ?? this.supportsLocationService, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/sentry_event.dart b/dart/lib/src/protocol/sentry_event.dart deleted file mode 100644 index 61b68decbd..0000000000 --- a/dart/lib/src/protocol/sentry_event.dart +++ /dev/null @@ -1,420 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:meta/meta.dart'; - -import '../protocol.dart'; -import '../throwable_mechanism.dart'; -import '../utils.dart'; -import 'access_aware_map.dart'; - -/// An event to be reported to Sentry.io. -class SentryEvent with SentryEventLike { - /// Creates an event. - SentryEvent({ - SentryId? eventId, - DateTime? timestamp, - Map? modules, - Map? tags, - @Deprecated( - 'Additional Data is deprecated in favor of structured [Contexts] and should be avoided when possible') - Map? extra, - List? fingerprint, - List? breadcrumbs, - List? exceptions, - List? threads, - this.sdk, - this.platform, - this.logger, - this.serverName, - this.release, - this.dist, - this.environment, - this.message, - this.transaction, - dynamic throwable, - this.level, - this.culprit, - this.user, - Contexts? contexts, - this.request, - this.debugMeta, - this.type, - this.unknown, - }) : eventId = eventId ?? SentryId.newId(), - timestamp = timestamp ?? getUtcDateTime(), - contexts = contexts ?? Contexts(), - modules = modules != null ? Map.from(modules) : null, - tags = tags != null ? Map.from(tags) : null, - // ignore: deprecated_member_use_from_same_package - extra = extra != null ? Map.from(extra) : null, - fingerprint = fingerprint != null ? List.from(fingerprint) : null, - breadcrumbs = breadcrumbs != null ? List.from(breadcrumbs) : null, - exceptions = exceptions != null ? List.from(exceptions) : null, - threads = threads != null ? List.from(threads) : null, - _throwable = throwable; - - /// Refers to the default fingerprinting algorithm. - /// - /// You do not need to specify this value unless you supplement the default - /// fingerprint with custom fingerprints. - static const String defaultFingerprint = '{{ default }}'; - - /// The ID Sentry.io assigned to the submitted event for future reference. - SentryId eventId; - - /// A timestamp representing when the event occurred. - DateTime? timestamp; - - /// A string representing the platform the SDK is submitting from. This will be used by the Sentry interface to customize various components in the interface. - String? platform; - - /// The logger that logged the event. - String? logger; - - /// Identifies the server that logged this event. - String? serverName; - - /// The version of the application that logged the event. - String? release; - - /// The distribution of the application. - String? dist; - - /// The environment that logged the event, e.g. "production", "staging". - String? environment; - - /// A list of relevant modules and their versions. - Map? modules; - - /// Event message. - /// - /// Generally an event either contains a [message] or [exceptions]. - SentryMessage? message; - - dynamic _throwable; - - /// An object that was thrown. - /// - /// It's `runtimeType` and `toString()` are logged. - /// If it's an Error, with a stackTrace, the stackTrace is logged. - /// If this behavior is undesirable, consider using a custom formatted - /// [message] instead. - dynamic get throwable => - (_throwable is ThrowableMechanism) ? _throwable.throwable : _throwable; - - /// A throwable decorator that holds a [Mechanism] related to the decorated - /// [throwable] - /// - /// Use the [throwable] directly if you don't want the decorated throwable - dynamic get throwableMechanism => _throwable; - - /// One or multiple chained (nested) exceptions that occurred in a program. - List? exceptions; - - /// The Threads Interface specifies threads that were running at the time an - /// event happened. These threads can also contain stack traces. - /// Typically not needed in Dart applications. - List? threads; - - /// The name of the transaction which generated this event, - /// for example, the route name: `"/users//"`. - String? transaction; - - /// How important this event is. - SentryLevel? level; - - /// What caused this event to be logged. - String? culprit; - - /// Name/value pairs that events can be searched by. - Map? tags; - - /// Arbitrary name/value pairs attached to the event. - /// - /// Sentry.io docs do not talk about restrictions on the values, other than - /// they must be JSON-serializable. - @Deprecated( - 'Additional Data is deprecated in favor of structured [Contexts] and should be avoided when possible') - Map? extra; - - /// List of breadcrumbs for this event. - /// - /// See also: - /// * https://docs.sentry.io/platforms/dart/enriching-events/breadcrumbs/ - /// * https://docs.sentry.io/platforms/flutter/enriching-events/breadcrumbs/ - List? breadcrumbs; - - /// Information about the current user. - /// - /// The value in this field overrides the user context - /// set in [Scope.user] for this logged event. - SentryUser? user; - - /// The context interfaces provide additional context data. - /// Typically this is data related to the current user, - /// the current HTTP request. - Contexts contexts; - - /// Used to deduplicate events by grouping ones with the same fingerprint - /// together. - /// - /// If not specified a default deduplication fingerprint is used. The default - /// fingerprint may be supplemented by additional fingerprints by specifying - /// multiple values. The default fingerprint can be specified by adding - /// [defaultFingerprint] to the list in addition to your custom values. - /// - /// Examples: - /// ```dart - /// // A completely custom fingerprint: - /// var custom = ['foo', 'bar', 'baz']; - /// // A fingerprint that supplements the default one with value 'foo': - /// var supplemented = [SentryEvent.defaultFingerprint, 'foo']; - /// ``` - List? fingerprint; - - /// The SDK Interface describes the Sentry SDK and its configuration used - /// to capture and transmit an event. - SdkVersion? sdk; - - /// Contains information on a HTTP request related to the event. - /// In client, this can be an outgoing request, or the request that rendered - /// the current web page. - /// On server, this could be the incoming web request that is being handled - SentryRequest? request; - - /// The debug meta interface carries debug information for processing errors - /// and crash reports. - DebugMeta? debugMeta; - - /// The event type determines how Sentry handles the event and has an impact - /// on processing, rate limiting, and quotas. - /// defaults to 'default' - String? type; - - @internal - final Map? unknown; - - @Deprecated('Assign values directly to the instance.') - @override - SentryEvent copyWith({ - SentryId? eventId, - DateTime? timestamp, - String? platform, - String? logger, - String? serverName, - String? release, - String? dist, - String? environment, - Map? modules, - SentryMessage? message, - String? transaction, - dynamic throwable, - SentryLevel? level, - String? culprit, - Map? tags, - @Deprecated( - 'Additional Data is deprecated in favor of structured [Contexts] and should be avoided when possible') - Map? extra, - List? fingerprint, - SentryUser? user, - Contexts? contexts, - List? breadcrumbs, - SdkVersion? sdk, - SentryRequest? request, - DebugMeta? debugMeta, - List? exceptions, - List? threads, - String? type, - }) => - SentryEvent( - eventId: eventId ?? this.eventId, - timestamp: timestamp ?? this.timestamp, - platform: platform ?? this.platform, - logger: logger ?? this.logger, - serverName: serverName ?? this.serverName, - release: release ?? this.release, - dist: dist ?? this.dist, - environment: environment ?? this.environment, - modules: (modules != null ? Map.from(modules) : null) ?? this.modules, - message: message ?? this.message, - transaction: transaction ?? this.transaction, - throwable: throwable ?? _throwable, - level: level ?? this.level, - culprit: culprit ?? this.culprit, - tags: (tags != null ? Map.from(tags) : null) ?? this.tags, - // ignore: deprecated_member_use_from_same_package - extra: (extra != null ? Map.from(extra) : null) ?? this.extra, - fingerprint: (fingerprint != null ? List.from(fingerprint) : null) ?? - this.fingerprint, - user: user ?? this.user, - contexts: contexts ?? this.contexts, - breadcrumbs: (breadcrumbs != null ? List.from(breadcrumbs) : null) ?? - this.breadcrumbs, - sdk: sdk ?? this.sdk, - request: request ?? this.request, - debugMeta: debugMeta ?? this.debugMeta, - exceptions: (exceptions != null ? List.from(exceptions) : null) ?? - this.exceptions, - threads: (threads != null ? List.from(threads) : null) ?? this.threads, - type: type ?? this.type, - unknown: unknown, - ); - - /// Deserializes a [SentryEvent] from JSON [Map]. - factory SentryEvent.fromJson(Map data) { - final json = AccessAwareMap(data); - - final breadcrumbsJson = json['breadcrumbs'] as List?; - final breadcrumbs = breadcrumbsJson - ?.map((e) => Breadcrumb.fromJson(e)) - .toList(growable: false); - - final threadValues = json['threads']?['values'] as List?; - final threads = threadValues - ?.map((e) => SentryThread.fromJson(e)) - .toList(growable: false); - - final exceptionValues = json['exception']?['values'] as List?; - final exceptions = exceptionValues - ?.map((e) => SentryException.fromJson(e)) - .toList(growable: false); - - final modules = json['modules']?.cast(); - final tags = json['tags']?.cast(); - - final timestampJson = json['timestamp']; - final levelJson = json['level']; - final fingerprintJson = json['fingerprint'] as List?; - final sdkVersionJson = json['sdk'] as Map?; - final messageJson = json['message'] as Map?; - final userJson = json['user'] as Map?; - final contextsJson = json['contexts'] as Map?; - final requestJson = json['request'] as Map?; - final debugMetaJson = json['debug_meta'] as Map?; - - var extra = json['extra']; - if (extra != null) { - extra = Map.from(extra as Map); - } - - return SentryEvent( - eventId: SentryId.fromId(json['event_id']), - timestamp: - timestampJson != null ? DateTime.tryParse(timestampJson) : null, - modules: modules, - tags: tags, - // ignore: deprecated_member_use_from_same_package - extra: extra, - fingerprint: - fingerprintJson?.map((e) => e as String).toList(growable: false), - breadcrumbs: breadcrumbs, - sdk: sdkVersionJson != null && sdkVersionJson.isNotEmpty - ? SdkVersion.fromJson(sdkVersionJson) - : null, - platform: json['platform'], - logger: json['logger'], - serverName: json['server_name'], - release: json['release'], - dist: json['dist'], - environment: json['environment'], - message: messageJson != null && messageJson.isNotEmpty - ? SentryMessage.fromJson(messageJson) - : null, - transaction: json['transaction'], - threads: threads, - level: levelJson != null ? SentryLevel.fromName(levelJson) : null, - culprit: json['culprit'], - user: userJson != null && userJson.isNotEmpty - ? SentryUser.fromJson(userJson) - : null, - contexts: contextsJson != null && contextsJson.isNotEmpty - ? Contexts.fromJson(contextsJson) - : null, - request: requestJson != null && requestJson.isNotEmpty - ? SentryRequest.fromJson(requestJson) - : null, - debugMeta: debugMetaJson != null && debugMetaJson.isNotEmpty - ? DebugMeta.fromJson(debugMetaJson) - : null, - exceptions: exceptions, - type: json['type'], - unknown: json.notAccessed(), - ); - } - - /// Serializes this event to JSON. - Map toJson() { - var messageMap = message?.toJson(); - final contextsMap = contexts.toJson(); - final userMap = user?.toJson(); - final sdkMap = sdk?.toJson(); - final requestMap = request?.toJson(); - final debugMetaMap = debugMeta?.toJson(); - final exceptionsJson = exceptions - ?.map((e) => e.toJson()) - .where((e) => e.isNotEmpty) - .toList(growable: false); - - // Thread serialization is tricky: - // - Thread should not have a stacktrace when an exception is connected to it - // - Thread should serializae a stacktrace when no exception is connected to it - - // These are the thread ids with a connected exception - final threadIds = exceptions - ?.map((element) => element.threadId) - .where((element) => element != null) - .toSet(); - - final threadJson = threads - ?.map((element) { - if (threadIds?.contains(element.id) ?? false) { - // remove thread.stacktrace if a connected exception exists - final json = element.toJson(); - json.remove('stacktrace'); - return json; - } - return element.toJson(); - }) - .where((e) => e.isNotEmpty) - .toList(growable: false); - - return { - ...?unknown, - 'event_id': eventId.toString(), - if (timestamp != null) - 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp!), - if (platform != null) 'platform': platform, - if (logger != null) 'logger': logger, - if (serverName != null) 'server_name': serverName, - if (release != null) 'release': release, - if (dist != null) 'dist': dist, - if (environment != null) 'environment': environment, - if (modules != null && modules!.isNotEmpty) 'modules': modules, - if (transaction != null) 'transaction': transaction, - if (level != null) 'level': level!.name, - if (culprit != null) 'culprit': culprit, - if (tags?.isNotEmpty ?? false) 'tags': tags, - // ignore: deprecated_member_use_from_same_package - if (extra?.isNotEmpty ?? false) 'extra': extra, - if (type != null) 'type': type, - if (fingerprint?.isNotEmpty ?? false) 'fingerprint': fingerprint, - if (breadcrumbs?.isNotEmpty ?? false) - 'breadcrumbs': - breadcrumbs?.map((b) => b.toJson()).toList(growable: false), - if (messageMap?.isNotEmpty ?? false) 'message': messageMap, - if (contextsMap.isNotEmpty) 'contexts': contextsMap, - if (userMap?.isNotEmpty ?? false) 'user': userMap, - if (sdkMap?.isNotEmpty ?? false) 'sdk': sdkMap, - if (requestMap?.isNotEmpty ?? false) 'request': requestMap, - if (debugMetaMap?.isNotEmpty ?? false) 'debug_meta': debugMetaMap, - if (exceptionsJson?.isNotEmpty ?? false) - 'exception': {'values': exceptionsJson}, - if (threadJson?.isNotEmpty ?? false) 'threads': {'values': threadJson}, - }; - } - - // Returns first non-null stack trace of this event - @internal - SentryStackTrace? get stacktrace => - exceptions?.firstWhereOrNull((e) => e.stackTrace != null)?.stackTrace ?? - threads?.firstWhereOrNull((t) => t.stacktrace != null)?.stacktrace; -} diff --git a/dart/lib/src/protocol/sentry_exception.dart b/dart/lib/src/protocol/sentry_exception.dart deleted file mode 100644 index a27d86c140..0000000000 --- a/dart/lib/src/protocol/sentry_exception.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../protocol.dart'; -import 'access_aware_map.dart'; - -/// The Exception Interface specifies an exception or error that occurred in a program. -class SentryException { - /// Required. The type of exception - String? type; - - /// Required. The value of the exception - String? value; - - /// The optional module, or package which the exception type lives in. - String? module; - - /// An optional stack trace object - SentryStackTrace? stackTrace; - - /// An optional object describing the [Mechanism] that created this exception - Mechanism? mechanism; - - /// Represents a [SentryThread.id]. - int? threadId; - - dynamic throwable; - - @internal - Map? unknown; - - List? _exceptions; - - SentryException({ - required this.type, - required this.value, - this.module, - this.stackTrace, - this.mechanism, - this.threadId, - this.throwable, - this.unknown, - }); - - /// Deserializes a [SentryException] from JSON [Map]. - factory SentryException.fromJson(Map data) { - final json = AccessAwareMap(data); - - final stackTraceJson = json['stacktrace']; - final mechanismJson = json['mechanism']; - return SentryException( - type: json['type'], - value: json['value'], - module: json['module'], - stackTrace: stackTraceJson != null - ? SentryStackTrace.fromJson(stackTraceJson) - : null, - mechanism: - mechanismJson != null ? Mechanism.fromJson(mechanismJson) : null, - threadId: json['thread_id'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - if (type != null) 'type': type, - if (value != null) 'value': value, - if (module != null) 'module': module, - if (stackTrace != null) 'stacktrace': stackTrace!.toJson(), - if (mechanism != null) 'mechanism': mechanism!.toJson(), - if (threadId != null) 'thread_id': threadId, - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryException copyWith({ - String? type, - String? value, - String? module, - SentryStackTrace? stackTrace, - Mechanism? mechanism, - int? threadId, - dynamic throwable, - }) => - SentryException( - type: type ?? this.type, - value: value ?? this.value, - module: module ?? this.module, - stackTrace: stackTrace ?? this.stackTrace?.copyWith(), - mechanism: mechanism ?? this.mechanism?.copyWith(), - threadId: threadId ?? this.threadId, - throwable: throwable ?? this.throwable, - unknown: unknown, - ); - - @internal - List? get exceptions => - _exceptions != null ? List.unmodifiable(_exceptions!) : null; - - @internal - set exceptions(List? value) { - _exceptions = value; - } - - @internal - void addException(SentryException exception) { - _exceptions ??= []; - _exceptions!.add(exception); - } -} diff --git a/dart/lib/src/protocol/sentry_feature_flag.dart b/dart/lib/src/protocol/sentry_feature_flag.dart deleted file mode 100644 index 9feb655424..0000000000 --- a/dart/lib/src/protocol/sentry_feature_flag.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -class SentryFeatureFlag { - final String flag; - final bool result; - - @internal - final Map? unknown; - - SentryFeatureFlag({ - required this.flag, - required this.result, - this.unknown, - }); - - factory SentryFeatureFlag.fromJson(Map data) { - final json = AccessAwareMap(data); - - return SentryFeatureFlag( - flag: json['flag'], - result: json['result'], - unknown: json.notAccessed(), - ); - } - - Map toJson() { - return { - ...?unknown, - 'flag': flag, - 'result': result, - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryFeatureFlag copyWith({ - String? flag, - bool? result, - Map? unknown, - }) { - return SentryFeatureFlag( - flag: flag ?? this.flag, - result: result ?? this.result, - unknown: unknown ?? this.unknown, - ); - } - - @Deprecated('Will be removed in a future version.') - SentryFeatureFlag clone() => copyWith(); -} diff --git a/dart/lib/src/protocol/sentry_feature_flags.dart b/dart/lib/src/protocol/sentry_feature_flags.dart deleted file mode 100644 index 324f0bbbbd..0000000000 --- a/dart/lib/src/protocol/sentry_feature_flags.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:meta/meta.dart'; -import 'sentry_feature_flag.dart'; -import 'access_aware_map.dart'; - -class SentryFeatureFlags { - static const type = 'flags'; - - List values; - - @internal - Map? unknown; - - SentryFeatureFlags({ - required this.values, - this.unknown, - }); - - factory SentryFeatureFlags.fromJson(Map data) { - final json = AccessAwareMap(data); - - final valuesValues = json['values'] as List?; - final values = valuesValues - ?.map((e) => SentryFeatureFlag.fromJson(Map.from(e))) - .toList(growable: false); - - return SentryFeatureFlags( - values: values ?? [], - unknown: json.notAccessed(), - ); - } - - Map toJson() { - return { - ...?unknown, - 'values': values.map((e) => e.toJson()).toList(growable: false), - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryFeatureFlags copyWith({ - List? values, - Map? unknown, - }) { - return SentryFeatureFlags( - values: values ?? - this.values.map((e) => e.copyWith()).toList(growable: false), - unknown: unknown ?? this.unknown, - ); - } - - @Deprecated('Will be removed in a future version.') - SentryFeatureFlags clone() => copyWith(); -} diff --git a/dart/lib/src/protocol/sentry_feedback.dart b/dart/lib/src/protocol/sentry_feedback.dart deleted file mode 100644 index a7af41407f..0000000000 --- a/dart/lib/src/protocol/sentry_feedback.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; -import 'sentry_id.dart'; - -class SentryFeedback { - static const type = 'feedback'; - - SentryFeedback({ - required this.message, - this.contactEmail, - this.name, - this.replayId, - this.url, - this.associatedEventId, - this.unknown, - }); - - String message; - String? contactEmail; - String? name; - String? replayId; - String? url; - SentryId? associatedEventId; - - @internal - final Map? unknown; - - /// Deserializes a [SentryFeedback] from JSON [Map]. - factory SentryFeedback.fromJson(Map data) { - final json = AccessAwareMap(data); - - String? associatedEventId = json['associated_event_id']; - - return SentryFeedback( - message: json['message'], - contactEmail: json['contact_email'], - name: json['name'], - replayId: json['replay_id'], - url: json['url'], - associatedEventId: - associatedEventId != null ? SentryId.fromId(associatedEventId) : null, - unknown: json.notAccessed(), - ); - } - - Map toJson() { - return { - ...?unknown, - 'message': message, - if (contactEmail != null) 'contact_email': contactEmail, - if (name != null) 'name': name, - if (replayId != null) 'replay_id': replayId, - if (url != null) 'url': url, - if (associatedEventId != null) - 'associated_event_id': associatedEventId.toString(), - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryFeedback copyWith({ - String? message, - String? contactEmail, - String? name, - String? replayId, - String? url, - SentryId? associatedEventId, - Map? unknown, - }) => - SentryFeedback( - message: message ?? this.message, - contactEmail: contactEmail ?? this.contactEmail, - name: name ?? this.name, - replayId: replayId ?? this.replayId, - url: url ?? this.url, - associatedEventId: associatedEventId ?? this.associatedEventId, - unknown: unknown ?? this.unknown, - ); - - @Deprecated('Will be removed in a future version.') - SentryFeedback clone() => copyWith(); -} diff --git a/dart/lib/src/protocol/sentry_geo.dart b/dart/lib/src/protocol/sentry_geo.dart deleted file mode 100644 index e8221c1537..0000000000 --- a/dart/lib/src/protocol/sentry_geo.dart +++ /dev/null @@ -1,29 +0,0 @@ -/// Geographical location of the end user or device. -class SentryGeo { - SentryGeo({this.city, this.countryCode, this.region}); - - factory SentryGeo.fromJson(Map json) { - return SentryGeo( - city: json['city'], - countryCode: json['country_code'], - region: json['region'], - ); - } - - /// Human readable city name. - final String? city; - - /// Two-letter country code (ISO 3166-1 alpha-2). - final String? countryCode; - - /// Human readable region name or code. - final String? region; - - Map toJson() { - return { - if (city != null) 'city': city, - if (countryCode != null) 'country_code': countryCode, - if (region != null) 'region': region, - }; - } -} diff --git a/dart/lib/src/protocol/sentry_gpu.dart b/dart/lib/src/protocol/sentry_gpu.dart deleted file mode 100644 index 6ef704978a..0000000000 --- a/dart/lib/src/protocol/sentry_gpu.dart +++ /dev/null @@ -1,202 +0,0 @@ -// https://develop.sentry.dev/sdk/event-payloads/contexts/#gpu-context -// Example: -// "gpu": { -// "name": "AMD Radeon Pro 560", -// "vendor_name": "Apple", -// "memory_size": 4096, -// "api_type": "Metal", -// "multi_threaded_rendering": true, -// "version": "Metal", -// "npot_support": "Full" -// } - -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -/// GPU context describes the GPU of the device. -class SentryGpu { - static const type = 'gpu'; - - /// The name of the graphics device. - String? name; - - /// The PCI identifier of the graphics device. - int? id; - - /// The PCI vendor identifier of the graphics device. - String? vendorId; - - /// The vendor name as reported by the graphics device. - String? vendorName; - - /// The total GPU memory available in Megabytes. - int? memorySize; - - /// The device low-level API type. - String? apiType; - - /// Whether the GPU has multi-threaded rendering or not. - bool? multiThreadedRendering; - - /// The Version of the graphics device. - String? version; - - /// The Non-Power-Of-Two-Support support. - String? npotSupport; - - /// Approximate "shader capability" level of the graphics device. - /// For Example: - /// Shader Model 2.0, OpenGL ES 3.0, Metal / OpenGL ES 3.1, 27 (unknown) - String? graphicsShaderLevel; - - /// Largest size of a texture that is supported by the graphics hardware. - /// For Example: 16384 - int? maxTextureSize; - - /// Whether compute shaders are available on the device. - bool? supportsComputeShaders; - - /// Whether GPU draw call instancing is supported. - bool? supportsDrawCallInstancing; - - /// Whether geometry shaders are available on the device. - bool? supportsGeometryShaders; - - /// Whether ray tracing is available on the device. - bool? supportsRayTracing; - - @internal - final Map? unknown; - - SentryGpu({ - this.name, - this.id, - this.vendorId, - this.vendorName, - this.memorySize, - this.apiType, - this.multiThreadedRendering, - this.version, - this.npotSupport, - this.graphicsShaderLevel, - this.maxTextureSize, - this.supportsComputeShaders, - this.supportsDrawCallInstancing, - this.supportsGeometryShaders, - this.supportsRayTracing, - this.unknown, - }); - - /// Deserializes a [SentryGpu] from JSON [Map]. - factory SentryGpu.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryGpu( - name: json['name'], - id: json['id'], - vendorId: json['vendor_id'], - vendorName: json['vendor_name'], - memorySize: json['memory_size'], - apiType: json['api_type'], - multiThreadedRendering: json['multi_threaded_rendering'], - version: json['version'], - npotSupport: json['npot_support'], - graphicsShaderLevel: json['graphics_shader_level'], - maxTextureSize: json['max_texture_size'], - supportsComputeShaders: json['supports_compute_shaders'], - supportsDrawCallInstancing: json['supports_draw_call_instancing'], - supportsGeometryShaders: json['supports_geometry_shaders'], - supportsRayTracing: json['supports_ray_tracing'], - unknown: json.notAccessed(), - ); - } - - @Deprecated('Will be removed in a future version.') - SentryGpu clone() => SentryGpu( - name: name, - id: id, - vendorId: vendorId, - vendorName: vendorName, - memorySize: memorySize, - apiType: apiType, - multiThreadedRendering: multiThreadedRendering, - version: version, - npotSupport: npotSupport, - graphicsShaderLevel: graphicsShaderLevel, - maxTextureSize: maxTextureSize, - supportsComputeShaders: supportsComputeShaders, - supportsDrawCallInstancing: supportsDrawCallInstancing, - supportsGeometryShaders: supportsGeometryShaders, - supportsRayTracing: supportsRayTracing, - unknown: unknown, - ); - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - if (name != null) 'name': name, - if (id != null) 'id': id, - if (vendorId != null) 'vendor_id': vendorId, - if (vendorName != null) 'vendor_name': vendorName, - if (memorySize != null) 'memory_size': memorySize, - if (apiType != null) 'api_type': apiType, - if (multiThreadedRendering != null) - 'multi_threaded_rendering': multiThreadedRendering, - if (version != null) 'version': version, - if (npotSupport != null) 'npot_support': npotSupport, - if (graphicsShaderLevel != null) - 'graphics_shader_level': graphicsShaderLevel, - if (maxTextureSize != null) 'max_texture_size': maxTextureSize, - if (supportsComputeShaders != null) - 'supports_compute_shaders': supportsComputeShaders, - if (supportsDrawCallInstancing != null) - 'supports_draw_call_instancing': supportsDrawCallInstancing, - if (supportsGeometryShaders != null) - 'supports_geometry_shaders': supportsGeometryShaders, - if (supportsRayTracing != null) - 'supports_ray_tracing': supportsRayTracing, - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryGpu copyWith({ - String? name, - int? id, - String? vendorId, - String? vendorName, - int? memorySize, - String? apiType, - bool? multiThreadedRendering, - String? version, - String? npotSupport, - String? graphicsShaderLevel, - int? maxTextureSize, - bool? supportsComputeShaders, - bool? supportsDrawCallInstancing, - bool? supportsGeometryShaders, - bool? supportsRayTracing, - }) => - SentryGpu( - name: name ?? this.name, - id: id ?? this.id, - vendorId: vendorId ?? this.vendorId, - vendorName: vendorName ?? this.vendorName, - memorySize: memorySize ?? this.memorySize, - apiType: apiType ?? this.apiType, - multiThreadedRendering: - multiThreadedRendering ?? this.multiThreadedRendering, - version: version ?? this.version, - npotSupport: npotSupport ?? this.npotSupport, - graphicsShaderLevel: graphicsShaderLevel ?? this.graphicsShaderLevel, - maxTextureSize: maxTextureSize ?? this.maxTextureSize, - supportsComputeShaders: - supportsComputeShaders ?? this.supportsComputeShaders, - supportsDrawCallInstancing: - supportsDrawCallInstancing ?? this.supportsDrawCallInstancing, - supportsGeometryShaders: - supportsGeometryShaders ?? this.supportsGeometryShaders, - supportsRayTracing: supportsRayTracing ?? this.supportsRayTracing, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/sentry_id.dart b/dart/lib/src/protocol/sentry_id.dart deleted file mode 100644 index 500c8980a2..0000000000 --- a/dart/lib/src/protocol/sentry_id.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:uuid/uuid.dart'; - -/// Hexadecimal string representing a uuid4 value. -/// The length is exactly 32 -/// characters. Dashes are not allowed. Has to be lowercase. -@immutable -class SentryId { - /// The ID Sentry.io assigned to the submitted event for future reference. - final String _id; - - static final Uuid _uuidGenerator = Uuid(); - - SentryId._internal({String? id}) - : _id = - id?.replaceAll('-', '') ?? _uuidGenerator.v4().replaceAll('-', ''); - - /// Generates a new SentryId - SentryId.newId() : this._internal(); - - /// Generates a SentryId with the given UUID - SentryId.fromId(String id) : this._internal(id: id); - - /// SentryId with an empty UUID - const SentryId.empty() : _id = '00000000000000000000000000000000'; - - @override - String toString() => _id; - - @override - int get hashCode => _id.hashCode; - - @override - bool operator ==(o) { - if (o is SentryId) { - return o._id == _id; - } - return false; - } -} diff --git a/dart/lib/src/protocol/sentry_level.dart b/dart/lib/src/protocol/sentry_level.dart deleted file mode 100644 index 264a7c43ab..0000000000 --- a/dart/lib/src/protocol/sentry_level.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Severity of the logged [Event]. -@immutable -class SentryLevel { - const SentryLevel._(this.name, this.ordinal); - - static const fatal = SentryLevel._('fatal', 5); - static const error = SentryLevel._('error', 4); - static const warning = SentryLevel._('warning', 3); - static const info = SentryLevel._('info', 2); - static const debug = SentryLevel._('debug', 1); - - /// API name of the level as it is encoded in the JSON protocol. - final String name; - final int ordinal; - - factory SentryLevel.fromName(String name) { - switch (name) { - case 'fatal': - return SentryLevel.fatal; - case 'error': - return SentryLevel.error; - case 'warning': - return SentryLevel.warning; - case 'info': - return SentryLevel.info; - } - return SentryLevel.debug; - } - - /// For use with Dart's - /// [`log`](https://api.dart.dev/stable/2.12.4/dart-developer/log.html) - /// function. - /// These levels are inspired by - /// https://pub.dev/documentation/logging/latest/logging/Level-class.html - int toDartLogLevel() { - switch (this) { - // Level.SHOUT - case SentryLevel.fatal: - return 1200; - // Level.SEVERE - case SentryLevel.error: - return 1000; - // Level.SEVERE - case SentryLevel.warning: - return 900; - // Level.INFO - case SentryLevel.info: - return 800; - // Level.CONFIG - case SentryLevel.debug: - return 700; - } - return 700; - } -} diff --git a/dart/lib/src/protocol/sentry_log.dart b/dart/lib/src/protocol/sentry_log.dart deleted file mode 100644 index 55c0174d90..0000000000 --- a/dart/lib/src/protocol/sentry_log.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'sentry_id.dart'; -import 'sentry_log_level.dart'; -import 'sentry_log_attribute.dart'; - -class SentryLog { - DateTime timestamp; - SentryId traceId; - SentryLogLevel level; - String body; - Map attributes; - int? severityNumber; - - /// The traceId is initially an empty default value and is populated during event processing; - /// by the time processing completes, it is guaranteed to be a valid non-empty trace id. - SentryLog({ - required this.timestamp, - SentryId? traceId, - required this.level, - required this.body, - required this.attributes, - this.severityNumber, - }) : traceId = traceId ?? SentryId.empty(); - - Map toJson() { - return { - 'timestamp': timestamp.toIso8601String(), - 'trace_id': traceId.toString(), - 'level': level.value, - 'body': body, - 'attributes': - attributes.map((key, value) => MapEntry(key, value.toJson())), - 'severity_number': severityNumber ?? level.toSeverityNumber(), - }; - } -} diff --git a/dart/lib/src/protocol/sentry_log_attribute.dart b/dart/lib/src/protocol/sentry_log_attribute.dart deleted file mode 100644 index 63ac85eb87..0000000000 --- a/dart/lib/src/protocol/sentry_log_attribute.dart +++ /dev/null @@ -1,30 +0,0 @@ -class SentryLogAttribute { - final dynamic value; - final String type; - - const SentryLogAttribute._(this.value, this.type); - - factory SentryLogAttribute.string(String value) { - return SentryLogAttribute._(value, 'string'); - } - - factory SentryLogAttribute.bool(bool value) { - return SentryLogAttribute._(value, 'boolean'); - } - - factory SentryLogAttribute.int(int value) { - return SentryLogAttribute._(value, 'integer'); - } - - factory SentryLogAttribute.double(double value) { - return SentryLogAttribute._(value, 'double'); - } - - // In the future the SDK will also support List, List, List, List values. - Map toJson() { - return { - 'value': value, - 'type': type, - }; - } -} diff --git a/dart/lib/src/protocol/sentry_log_level.dart b/dart/lib/src/protocol/sentry_log_level.dart deleted file mode 100644 index aac0b386bc..0000000000 --- a/dart/lib/src/protocol/sentry_log_level.dart +++ /dev/null @@ -1,28 +0,0 @@ -enum SentryLogLevel { - trace('trace'), - debug('debug'), - info('info'), - warn('warn'), - error('error'), - fatal('fatal'); - - final String value; - const SentryLogLevel(this.value); - - int toSeverityNumber() { - switch (this) { - case SentryLogLevel.trace: - return 1; - case SentryLogLevel.debug: - return 5; - case SentryLogLevel.info: - return 9; - case SentryLogLevel.warn: - return 13; - case SentryLogLevel.error: - return 17; - case SentryLogLevel.fatal: - return 21; - } - } -} diff --git a/dart/lib/src/protocol/sentry_message.dart b/dart/lib/src/protocol/sentry_message.dart deleted file mode 100644 index 9cddad8851..0000000000 --- a/dart/lib/src/protocol/sentry_message.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -/// The Message Interface carries a log message that describes an event or error. -/// Optionally, it can carry a format string and structured parameters. This can help to group similar messages into the same issue. -/// example of a serialized message: -/// ```json -/// { -/// "message": { -/// "message": "My raw message with interpreted strings like %s", -/// "params": ["this"] -/// } -/// } -/// ``` -class SentryMessage { - /// The fully formatted message. If missing, Sentry will try to interpolate the message. - String formatted; - - /// The raw message string (uninterpolated). - /// example : "My raw message with interpreted strings like %s", - String? template; - - /// A list of formatting parameters, preferably strings. Non-strings will be coerced to strings. - List? params; - - @internal - final Map? unknown; - - SentryMessage( - this.formatted, { - this.template, - this.params, - this.unknown, - }); - - /// Deserializes a [SentryMessage] from JSON [Map]. - factory SentryMessage.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryMessage( - json['formatted'], - template: json['message'], - params: json['params'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - 'formatted': formatted, - if (template != null) 'message': template, - if (params?.isNotEmpty ?? false) 'params': params, - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryMessage copyWith({ - String? formatted, - String? template, - List? params, - }) => - SentryMessage( - formatted ?? this.formatted, - template: template ?? this.template, - params: params ?? this.params, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/sentry_operating_system.dart b/dart/lib/src/protocol/sentry_operating_system.dart deleted file mode 100644 index 50f5e4f8ae..0000000000 --- a/dart/lib/src/protocol/sentry_operating_system.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -/// Describes the operating system on which the event was created. -/// -/// In web contexts, this is the operating system of the browse -/// (normally pulled from the User-Agent string). -class SentryOperatingSystem { - static const type = 'os'; - - SentryOperatingSystem({ - this.name, - this.version, - this.build, - this.kernelVersion, - this.rooted, - this.rawDescription, - this.theme, - this.unknown, - }); - - /// The name of the operating system. - String? name; - - /// The version of the operating system. - String? version; - - /// The internal build revision of the operating system. - String? build; - - /// An independent kernel version string. - /// - /// This is typically the entire output of the `uname` syscall. - String? kernelVersion; - - /// A flag indicating whether the OS has been jailbroken or rooted. - bool? rooted; - - /// An unprocessed description string obtained by the operating system. - /// - /// For some well-known runtimes, Sentry will attempt to parse name and - /// version from this string, if they are not explicitly given. - String? rawDescription; - - /// Optional. Either light or dark. - /// Describes whether the OS runs in dark mode or not. - String? theme; - - @internal - final Map? unknown; - - /// Deserializes a [SentryOperatingSystem] from JSON [Map]. - factory SentryOperatingSystem.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryOperatingSystem( - name: json['name'], - version: json['version'], - build: json['build'], - kernelVersion: json['kernel_version'], - rooted: json['rooted'], - rawDescription: json['raw_description'], - theme: json['theme'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - if (name != null) 'name': name, - if (version != null) 'version': version, - if (build != null) 'build': build, - if (kernelVersion != null) 'kernel_version': kernelVersion, - if (rooted != null) 'rooted': rooted, - if (rawDescription != null) 'raw_description': rawDescription, - if (theme != null) 'theme': theme, - }; - } - - @Deprecated('Will be removed in a future version.') - SentryOperatingSystem clone() => SentryOperatingSystem( - name: name, - version: version, - build: build, - kernelVersion: kernelVersion, - rooted: rooted, - rawDescription: rawDescription, - theme: theme, - unknown: unknown, - ); - - @Deprecated('Assign values directly to the instance.') - SentryOperatingSystem copyWith({ - String? name, - String? version, - String? build, - String? kernelVersion, - bool? rooted, - String? rawDescription, - String? theme, - }) => - SentryOperatingSystem( - name: name ?? this.name, - version: version ?? this.version, - build: build ?? this.build, - kernelVersion: kernelVersion ?? this.kernelVersion, - rooted: rooted ?? this.rooted, - rawDescription: rawDescription ?? this.rawDescription, - theme: theme ?? this.theme, - unknown: unknown, - ); - - SentryOperatingSystem mergeWith(SentryOperatingSystem other) => - SentryOperatingSystem( - name: other.name ?? name, - version: other.version ?? version, - build: other.build ?? build, - kernelVersion: other.kernelVersion ?? kernelVersion, - rooted: other.rooted ?? rooted, - rawDescription: other.rawDescription ?? rawDescription, - theme: other.theme ?? theme, - unknown: other.unknown == null - ? unknown - : unknown == null - ? null - : {...unknown!, ...other.unknown!}, - ); -} diff --git a/dart/lib/src/protocol/sentry_package.dart b/dart/lib/src/protocol/sentry_package.dart deleted file mode 100644 index f0e2491232..0000000000 --- a/dart/lib/src/protocol/sentry_package.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -/// A [SentryPackage] part of the SDK. -class SentryPackage { - /// Creates an [SentryPackage] object that is part of the SDK. - SentryPackage(this.name, this.version, {this.unknown}); - - /// The name of the SDK. - String name; - - /// The version of the SDK. - String version; - - @internal - final Map? unknown; - - /// Deserializes a [SentryPackage] from JSON [Map]. - factory SentryPackage.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryPackage( - json['name'], - json['version'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - 'name': name, - 'version': version, - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryPackage copyWith({ - String? name, - String? version, - }) => - SentryPackage( - name ?? this.name, - version ?? this.version, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/sentry_proxy.dart b/dart/lib/src/protocol/sentry_proxy.dart deleted file mode 100644 index 52237e9b1f..0000000000 --- a/dart/lib/src/protocol/sentry_proxy.dart +++ /dev/null @@ -1,63 +0,0 @@ -class SentryProxy { - final SentryProxyType type; - final String? host; - final int? port; - final String? user; - final String? pass; - - SentryProxy({required this.type, this.host, this.port, this.user, this.pass}); - - String toPacString() { - String type = 'DIRECT'; - switch (this.type) { - case SentryProxyType.direct: - return 'DIRECT'; - case SentryProxyType.http: - type = 'PROXY'; - break; - case SentryProxyType.socks: - type = 'SOCKS'; - break; - } - if (host != null && port != null) { - return '$type $host:$port'; - } else if (host != null) { - return '$type $host'; - } else { - return 'DIRECT'; - } - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - if (host != null) 'host': host, - if (port != null) 'port': port, - 'type': type.toString().split('.').last.toUpperCase(), - if (user != null) 'user': user, - if (pass != null) 'pass': pass, - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryProxy copyWith({ - String? host, - int? port, - SentryProxyType? type, - String? user, - String? pass, - }) => - SentryProxy( - host: host ?? this.host, - port: port ?? this.port, - type: type ?? this.type, - user: user ?? this.user, - pass: pass ?? this.pass, - ); -} - -enum SentryProxyType { - direct, - http, - socks; -} diff --git a/dart/lib/src/protocol/sentry_request.dart b/dart/lib/src/protocol/sentry_request.dart deleted file mode 100644 index 6bdae7a352..0000000000 --- a/dart/lib/src/protocol/sentry_request.dart +++ /dev/null @@ -1,178 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../utils/http_sanitizer.dart'; -import '../utils/iterable_utils.dart'; -import 'access_aware_map.dart'; - -/// The Request interface contains information on a HTTP request related to the event. -/// In client SDKs, this can be an outgoing request, or the request that rendered the current web page. -/// On server SDKs, this could be the incoming web request that is being handled. -class SentryRequest { - ///The URL of the request if available. - ///The query string can be declared either as part of the url, - ///or separately in queryString. - String? url; - - ///The HTTP method of the request. - String? method; - - /// The query string component of the URL. - /// - /// If the query string is not declared and part of the url parameter, - /// Sentry moves it to the query string. - String? queryString; - - /// The cookie values as string. - String? cookies; - - dynamic _data; - - /// Submitted data in a format that makes the most sense. - /// SDKs should discard large bodies by default. - /// Can be given as string or structural data of any format. - dynamic get data { - if (_data is List) { - return List.unmodifiable(_data); - } else if (_data is Map) { - return Map.unmodifiable(_data); - } - - return _data; - } - - Map? _headers; - - /// An immutable dictionary of submitted headers. - /// If a header appears multiple times it, - /// needs to be merged according to the HTTP standard for header merging. - /// Header names are treated case-insensitively by Sentry. - Map get headers => Map.unmodifiable(_headers ?? const {}); - - set headers(Map headers) { - _headers = Map.of(headers); - } - - Map? _env; - - /// An immutable dictionary containing environment information passed from the server. - /// This is where information such as CGI/WSGI/Rack keys go that are not HTTP headers. - Map get env => Map.unmodifiable(_env ?? const {}); - - /// The fragment of the request URL. - String? fragment; - - /// The API target/specification that made the request. - /// Values can be `graphql`, `rest`, etc. - /// - /// The data field should contain the request and response bodies based on - /// its target specification. - String? apiTarget; - - @internal - final Map? unknown; - - SentryRequest({ - this.url, - this.method, - this.queryString, - String? cookies, - this.fragment, - this.apiTarget, - dynamic data, - Map? headers, - Map? env, - this.unknown, - }) : _data = data, - _headers = headers != null ? Map.from(headers) : null, - // Look for a 'Set-Cookie' header (case insensitive) if not given. - cookies = cookies ?? - IterableUtils.firstWhereOrNull( - headers?.entries, - (MapEntry e) => e.key.toLowerCase() == 'cookie', - )?.value, - _env = env != null ? Map.from(env) : null; - - factory SentryRequest.fromUri({ - required Uri uri, - String? method, - String? cookies, - dynamic data, - Map? headers, - Map? env, - String? apiTarget, - }) { - final request = SentryRequest( - url: uri.toString(), - method: method, - cookies: cookies, - data: data, - headers: headers, - env: env, - queryString: uri.query, - fragment: uri.fragment, - // ignore: deprecated_member_use_from_same_package - apiTarget: apiTarget, - ); - request.sanitize(); - return request; - } - - /// Deserializes a [SentryRequest] from JSON [Map]. - factory SentryRequest.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryRequest( - url: json['url'], - method: json['method'], - queryString: json['query_string'], - cookies: json['cookies'], - data: json['data'], - headers: json.containsKey('headers') ? Map.from(json['headers']) : null, - env: json.containsKey('env') ? Map.from(json['env']) : null, - fragment: json['fragment'], - apiTarget: json['api_target'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - if (url != null) 'url': url, - if (method != null) 'method': method, - if (queryString != null) 'query_string': queryString, - if (_data != null) 'data': _data, - if (cookies != null) 'cookies': cookies, - if (headers.isNotEmpty) 'headers': headers, - if (env.isNotEmpty) 'env': env, - if (fragment != null) 'fragment': fragment, - if (apiTarget != null) 'api_target': apiTarget, - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryRequest copyWith({ - String? url, - String? method, - String? queryString, - String? cookies, - String? fragment, - dynamic data, - Map? headers, - Map? env, - bool removeCookies = false, - String? apiTarget, - }) => - SentryRequest( - url: url ?? this.url, - method: method ?? this.method, - queryString: queryString ?? this.queryString, - cookies: removeCookies ? null : cookies ?? this.cookies, - data: data ?? _data, - headers: headers ?? _headers, - env: env ?? _env, - fragment: fragment ?? this.fragment, - apiTarget: apiTarget ?? this.apiTarget, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/sentry_response.dart b/dart/lib/src/protocol/sentry_response.dart deleted file mode 100644 index c7aa527835..0000000000 --- a/dart/lib/src/protocol/sentry_response.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'contexts.dart'; -import '../utils/iterable_utils.dart'; - -/// The response interface contains information on a HTTP request related to the event. -class SentryResponse { - /// The type of this class in the [Contexts] field - static const String type = 'response'; - - /// The size of the response body. - int? bodySize; - - /// The HTTP status code of the response. - /// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status - int? statusCode; - - /// An immutable dictionary of submitted headers. - /// If a header appears multiple times it, - /// needs to be merged according to the HTTP standard for header merging. - /// Header names are treated case-insensitively by Sentry. - Map get headers => Map.unmodifiable(_headers ?? const {}); - - Map? _headers; - - /// Cookie key-value pairs as string. - String? cookies; - - Object? _data; - - /// Response data in any format that makes sense. - /// - /// SDKs should discard large and binary bodies by default. - /// Can be given as a string or structural data of any format. - Object? get data { - final typedData = _data; - if (typedData is List) { - return List.unmodifiable(typedData); - } else if (typedData is Map) { - return Map.unmodifiable(typedData); - } - - return _data; - } - - SentryResponse({ - this.bodySize, - this.statusCode, - Map? headers, - String? cookies, - Object? data, - }) : _data = data, - _headers = headers != null ? Map.from(headers) : null, - // Look for a 'Set-Cookie' header (case insensitive) if not given. - cookies = cookies ?? - IterableUtils.firstWhereOrNull( - headers?.entries, - (MapEntry e) => - e.key.toLowerCase() == 'set-cookie', - )?.value; - - /// Deserializes a [SentryResponse] from JSON [Map]. - factory SentryResponse.fromJson(Map json) { - return SentryResponse( - headers: json.containsKey('headers') ? Map.from(json['headers']) : null, - cookies: json['cookies'], - bodySize: json['body_size'], - statusCode: json['status_code'], - data: json['data'], - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - if (headers.isNotEmpty) 'headers': headers, - if (cookies != null) 'cookies': cookies, - if (bodySize != null) 'body_size': bodySize, - if (statusCode != null) 'status_code': statusCode, - if (data != null) 'data': data, - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryResponse copyWith({ - int? statusCode, - int? bodySize, - Map? headers, - String? cookies, - Object? data, - }) => - SentryResponse( - headers: headers ?? _headers, - cookies: cookies ?? this.cookies, - bodySize: bodySize ?? this.bodySize, - statusCode: statusCode ?? this.statusCode, - data: data ?? this.data, - ); - - @Deprecated('Will be removed in a future version.') - SentryResponse clone() => SentryResponse( - bodySize: bodySize, - headers: headers, - cookies: cookies, - statusCode: statusCode, - data: data, - ); -} diff --git a/dart/lib/src/protocol/sentry_runtime.dart b/dart/lib/src/protocol/sentry_runtime.dart deleted file mode 100644 index 9fd8e552ab..0000000000 --- a/dart/lib/src/protocol/sentry_runtime.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -/// Describes a runtime in more detail. -/// -/// Typically this context is used multiple times if multiple runtimes -/// are involved (for instance if you have a JavaScript application running -/// on top of JVM). -class SentryRuntime { - static const listType = 'runtimes'; - static const type = 'runtime'; - - SentryRuntime({ - this.key, - this.name, - this.version, - this.compiler, - this.rawDescription, - this.build, - this.unknown, - }) : assert(key == null || key.isNotEmpty); - - /// Key used in the JSON and which will be displayed - /// in the Sentry UI. Defaults to lower case version of [name]. - /// - /// Unused if only one [SentryRuntime] is provided in [Contexts]. - String? key; - - /// The name of the runtime. - String? name; - - /// The version identifier of the runtime. - String? version; - - /// Dart has a couple different compilers. - /// E.g: dart2js, dartdevc, AOT, VM - String? compiler; - - /// An unprocessed description string obtained by the runtime. - /// - /// For some well-known runtimes, Sentry will attempt to parse name - /// and version from this string, if they are not explicitly given. - String? rawDescription; - - /// Application build string, if it is separate from the version. - String? build; - - @internal - final Map? unknown; - - /// Deserializes a [SentryRuntime] from JSON [Map]. - factory SentryRuntime.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryRuntime( - name: json['name'], - version: json['version'], - compiler: json['compiler'], - rawDescription: json['raw_description'], - build: json['build'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - if (name != null) 'name': name, - if (compiler != null) 'compiler': compiler, - if (version != null) 'version': version, - if (rawDescription != null) 'raw_description': rawDescription, - if (build != null) 'build': build, - }; - } - - @Deprecated('Will be removed in a future version.') - SentryRuntime clone() => SentryRuntime( - key: key, - name: name, - version: version, - compiler: compiler, - rawDescription: rawDescription, - build: build, - unknown: unknown, - ); - - SentryRuntime copyWith({ - String? key, - String? name, - String? version, - String? compiler, - String? rawDescription, - String? build, - }) => - SentryRuntime( - key: key ?? this.key, - name: name ?? this.name, - version: version ?? this.version, - compiler: compiler ?? this.compiler, - rawDescription: rawDescription ?? this.rawDescription, - build: build ?? this.build, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/sentry_span.dart b/dart/lib/src/protocol/sentry_span.dart deleted file mode 100644 index e93a5f96fd..0000000000 --- a/dart/lib/src/protocol/sentry_span.dart +++ /dev/null @@ -1,244 +0,0 @@ -import 'dart:async'; - -import 'package:meta/meta.dart'; - -import '../../sentry.dart'; -import '../sentry_tracer.dart'; - -typedef OnFinishedCallback = Future Function( - {DateTime? endTimestamp, Hint? hint}); - -class SentrySpan extends ISentrySpan { - final SentrySpanContext _context; - DateTime? _endTimestamp; - late final DateTime _startTimestamp; - final Hub _hub; - - bool _isRootSpan = false; - - bool get isRootSpan => _isRootSpan; - - @internal - SentryTracer get tracer => _tracer; - - final SentryTracer _tracer; - - final Map _data = {}; - dynamic _throwable; - - SpanStatus? _status; - final Map _tags = {}; - OnFinishedCallback? _finishedCallback; - - @override - final SentryTracesSamplingDecision? samplingDecision; - - SentrySpan( - this._tracer, - this._context, - this._hub, { - DateTime? startTimestamp, - this.samplingDecision, - OnFinishedCallback? finishedCallback, - isRootSpan = false, - }) { - _startTimestamp = startTimestamp?.toUtc() ?? _hub.options.clock(); - _finishedCallback = finishedCallback; - _origin = _context.origin; - _isRootSpan = isRootSpan; - } - - @override - Future finish( - {SpanStatus? status, DateTime? endTimestamp, Hint? hint}) async { - if (finished) { - return; - } - - if (status != null) { - _status = status; - } - - if (endTimestamp == null) { - endTimestamp = _hub.options.clock(); - } else if (endTimestamp.isBefore(_startTimestamp)) { - _hub.options.log( - SentryLevel.warning, - 'End timestamp ($endTimestamp) cannot be before start timestamp ($_startTimestamp)', - ); - endTimestamp = _hub.options.clock(); - } else { - endTimestamp = endTimestamp.toUtc(); - } - - for (final collector in _hub.options.performanceCollectors) { - if (collector is PerformanceContinuousCollector) { - await collector.onSpanFinished(this, endTimestamp); - } - } - - // The finished flag depends on the _endTimestamp - // If we set this earlier then finished is true and then we cannot use setData etc... - _endTimestamp = endTimestamp; - - // associate error - if (_throwable != null) { - _hub.setSpanContext(_throwable, this, _tracer.name); - } - await _finishedCallback?.call(endTimestamp: _endTimestamp, hint: hint); - return super - .finish(status: status, endTimestamp: _endTimestamp, hint: hint); - } - - @override - void removeData(String key) { - if (finished) { - return; - } - - _data.remove(key); - } - - @override - void removeTag(String key) { - if (finished) { - return; - } - - _tags.remove(key); - } - - @override - void setData(String key, value) { - if (finished) { - return; - } - - _data[key] = value; - } - - @override - void setTag(String key, String value) { - if (finished) { - return; - } - - _tags[key] = value; - } - - @override - ISentrySpan startChild( - String operation, { - String? description, - DateTime? startTimestamp, - }) { - if (finished) { - return NoOpSentrySpan(); - } - - if (startTimestamp?.isBefore(_startTimestamp) ?? false) { - _hub.options.log( - SentryLevel.warning, - "Start timestamp ($startTimestamp) cannot be before parent span's start timestamp ($_startTimestamp). Returning NoOpSpan.", - ); - return NoOpSentrySpan(); - } - - return _tracer.startChildWithParentSpanId( - _context.spanId, - operation, - description: description, - startTimestamp: startTimestamp, - ); - } - - @override - SpanStatus? get status => _status; - - @override - set status(SpanStatus? status) => _status = status; - - @override - DateTime get startTimestamp => _startTimestamp; - - @override - DateTime? get endTimestamp => _endTimestamp; - - @override - SentrySpanContext get context => _context; - - String? _origin; - - @override - String? get origin => _origin; - - @override - set origin(String? origin) => _origin = origin; - - Map toJson() { - final json = _context.toJson(); - json['start_timestamp'] = - formatDateAsIso8601WithMillisPrecision(_startTimestamp); - if (_endTimestamp != null) { - json['timestamp'] = - formatDateAsIso8601WithMillisPrecision(_endTimestamp!); - } - if (_data.isNotEmpty) { - json['data'] = _data; - } - if (status != null) { - json['status'] = status.toString(); - } - if (_tags.isNotEmpty) { - json['tags'] = _tags; - } - if (_origin != null) { - json['origin'] = _origin; - } - - return json; - } - - @override - bool get finished => _endTimestamp != null; - - @override - dynamic get throwable => _throwable; - - @override - set throwable(throwable) => _throwable = throwable; - - Map get tags => _tags; - - Map get data => _data; - - @override - SentryTraceHeader toSentryTrace() => generateSentryTraceHeader( - traceId: _context.traceId, - spanId: _context.spanId, - sampled: samplingDecision?.sampled, - ); - - @override - void setMeasurement( - String name, - num value, { - SentryMeasurementUnit? unit, - }) { - if (finished) { - _hub.options.log(SentryLevel.debug, - "The span is already finished. Measurement $name cannot be set"); - return; - } - _tracer.setMeasurementFromChild(name, value, unit: unit); - } - - @override - SentryBaggageHeader? toBaggageHeader() => _tracer.toBaggageHeader(); - - @override - SentryTraceContextHeader? traceContext() => _tracer.traceContext(); - - @override - void scheduleFinish() => _tracer.scheduleFinish(); -} diff --git a/dart/lib/src/protocol/sentry_stack_frame.dart b/dart/lib/src/protocol/sentry_stack_frame.dart deleted file mode 100644 index 244f39a037..0000000000 --- a/dart/lib/src/protocol/sentry_stack_frame.dart +++ /dev/null @@ -1,237 +0,0 @@ -import 'package:meta/meta.dart'; -import 'access_aware_map.dart'; - -/// Frames belong to a StackTrace -/// It should contain at least a filename, function or instruction_addr -class SentryStackFrame { - SentryStackFrame({ - this.absPath, - this.fileName, - this.function, - this.module, - this.lineNo, - this.colNo, - this.contextLine, - this.inApp, - this.package, - this.native, - this.platform, - this.imageAddr, - this.symbolAddr, - this.instructionAddr, - this.rawFunction, - this.stackStart, - this.symbol, - List? framesOmitted, - List? preContext, - List? postContext, - Map? vars, - this.unknown, - }) : _framesOmitted = - framesOmitted != null ? List.from(framesOmitted) : null, - _preContext = preContext != null ? List.from(preContext) : null, - _postContext = postContext != null ? List.from(postContext) : null, - _vars = vars != null ? Map.from(vars) : null; - - /// The absolute path to filename. - String? absPath; - - List? _preContext; - - /// An immutable list of source code lines before context_line (in order) – usually `lineno - 5:lineno`. - List get preContext => List.unmodifiable(_preContext ?? const []); - - List? _postContext; - - /// An immutable list of source code lines after context_line (in order) – usually `lineno + 1:lineno + 5`. - List get postContext => List.unmodifiable(_postContext ?? const []); - - Map? _vars; - - /// An immutable mapping of variables which were available within this frame (usually context-locals). - Map get vars => Map.unmodifiable(_vars ?? const {}); - - List? _framesOmitted; - - /// Which frames were omitted, if any. - /// - /// If the list of frames is large, you can explicitly tell the system - /// that you’ve omitted a range of frames. - /// The frames_omitted must be a single tuple two values: start and end. - // - /// Example : If you only removed the 8th frame, the value would be (8, 9), - /// meaning it started at the 8th frame, and went until the 9th (the number of frames omitted is end-start). - /// The values should be based on a one-index. - List get framesOmitted => List.unmodifiable(_framesOmitted ?? const []); - - /// The relative file path to the call. - String? fileName; - - /// The name of the function being called. - String? function; - - /// Platform-specific module path. - String? module; - - /// The column number of the call - int? lineNo; - - /// The column number of the call - int? colNo; - - /// Source code in filename at line number. - String? contextLine; - - /// Signifies whether this frame is related to the execution of the relevant code in this stacktrace. - /// - /// For example, the frames that might power the framework’s web server of your app are probably not relevant, however calls to the framework’s library once you start handling code likely are. - bool? inApp; - - /// The "package" the frame was contained in. - String? package; - - // TODO what is this? doesn't seem to be part of the spec https://develop.sentry.dev/sdk/event-payloads/stacktrace/ - bool? native; - - /// This can override the platform for a single frame. Otherwise, the platform of the event is assumed. This can be used for multi-platform stack traces - String? platform; - - /// Optionally an address of the debug image to reference. - String? imageAddr; - - /// An optional address that points to a symbol. We use the instruction address for symbolication, but this can be used to calculate an instruction offset automatically. - String? symbolAddr; - - /// The instruction address - /// The official docs refer to it as 'The difference between instruction address and symbol address in bytes.' - String? instructionAddr; - - /// The original function name, if the function name is shortened or demangled. Sentry shows the raw function when clicking on the shortened one in the UI. - String? rawFunction; - - /// Marks this frame as the bottom of a chained stack trace. - /// - /// Stack traces from asynchronous code consist of several sub traces that - /// are chained together into one large list. This flag indicates the root - /// function of a chained stack trace. Depending on the runtime and thread, - /// this is either the main function or a thread base stub. - /// - /// This field should only be specified when true. - bool? stackStart; - - /// Potentially mangled name of the symbol as it appears in an executable. - /// - /// This is different from a function name by generally being the mangled name - /// that appears natively in the binary. - /// This is relevant for languages like Swift, C++ or Rust. - String? symbol; - - @internal - final Map? unknown; - - /// Deserializes a [SentryStackFrame] from JSON [Map]. - factory SentryStackFrame.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryStackFrame( - absPath: json['abs_path'], - fileName: json['filename'], - function: json['function'], - module: json['module'], - lineNo: json['lineno'], - colNo: json['colno'], - contextLine: json['context_line'], - inApp: json['in_app'], - package: json['package'], - native: json['native'], - platform: json['platform'], - imageAddr: json['image_addr'], - symbolAddr: json['symbol_addr'], - instructionAddr: json['instruction_addr'], - rawFunction: json['raw_function'], - framesOmitted: json['frames_omitted'], - preContext: json['pre_context'], - postContext: json['post_context'], - vars: json['vars'], - symbol: json['symbol'], - stackStart: json['stack_start'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - if (_preContext?.isNotEmpty ?? false) 'pre_context': _preContext, - if (_postContext?.isNotEmpty ?? false) 'post_context': _postContext, - if (_vars?.isNotEmpty ?? false) 'vars': _vars, - if (_framesOmitted?.isNotEmpty ?? false) 'frames_omitted': _framesOmitted, - if (fileName != null) 'filename': fileName, - if (package != null) 'package': package, - if (function != null) 'function': function, - if (module != null) 'module': module, - if (lineNo != null) 'lineno': lineNo, - if (colNo != null) 'colno': colNo, - if (absPath != null) 'abs_path': absPath, - if (contextLine != null) 'context_line': contextLine, - if (inApp != null) 'in_app': inApp, - if (native != null) 'native': native, - if (platform != null) 'platform': platform, - if (imageAddr != null) 'image_addr': imageAddr, - if (symbolAddr != null) 'symbol_addr': symbolAddr, - if (instructionAddr != null) 'instruction_addr': instructionAddr, - if (rawFunction != null) 'raw_function': rawFunction, - if (symbol != null) 'symbol': symbol, - if (stackStart != null) 'stack_start': stackStart, - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryStackFrame copyWith({ - String? absPath, - String? fileName, - String? function, - String? module, - int? lineNo, - int? colNo, - String? contextLine, - bool? inApp, - String? package, - bool? native, - String? platform, - String? imageAddr, - String? symbolAddr, - String? instructionAddr, - String? rawFunction, - List? framesOmitted, - List? preContext, - List? postContext, - Map? vars, - bool? stackStart, - String? symbol, - }) => - SentryStackFrame( - absPath: absPath ?? this.absPath, - fileName: fileName ?? this.fileName, - function: function ?? this.function, - module: module ?? this.module, - lineNo: lineNo ?? this.lineNo, - colNo: colNo ?? this.colNo, - contextLine: contextLine ?? this.contextLine, - inApp: inApp ?? this.inApp, - package: package ?? this.package, - native: native ?? this.native, - platform: platform ?? this.platform, - imageAddr: imageAddr ?? this.imageAddr, - symbolAddr: symbolAddr ?? this.symbolAddr, - instructionAddr: instructionAddr ?? this.instructionAddr, - rawFunction: rawFunction ?? this.rawFunction, - framesOmitted: framesOmitted ?? _framesOmitted, - preContext: preContext ?? _preContext, - postContext: postContext ?? _postContext, - vars: vars ?? _vars, - symbol: symbol ?? symbol, - stackStart: stackStart ?? stackStart, - unknown: unknown, - ); -} diff --git a/dart/lib/src/protocol/sentry_stack_trace.dart b/dart/lib/src/protocol/sentry_stack_trace.dart deleted file mode 100644 index c6908d9863..0000000000 --- a/dart/lib/src/protocol/sentry_stack_trace.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'sentry_stack_frame.dart'; -import 'access_aware_map.dart'; - -/// Stacktrace holds information about the frames of the stack. -class SentryStackTrace { - SentryStackTrace({ - required List frames, - Map? registers, - this.lang, - this.snapshot, - this.unknown, - @internal this.baseAddr, - @internal this.buildId, - }) : _frames = frames, - _registers = Map.from(registers ?? {}); - - List? _frames; - - /// Required. A non-empty immutable list of stack frames (see below). - /// The list is ordered from caller to callee, or oldest to youngest. - /// The last frame is the one creating the exception. - List get frames => List.unmodifiable(_frames ?? const []); - - Map? _registers; - - /// Optional. A map of register names and their values. - /// The values should contain the actual register values of the thread, - /// thus mapping to the last frame in the list. - Map get registers => Map.unmodifiable(_registers ?? const {}); - - /// The language of the stacktrace - String? lang; - - /// Indicates that this stack trace is a snapshot triggered - /// by an external signal. - /// - /// If this field is false, then the stack trace points to the code that - /// caused this stack trace to be created. - /// This can be the location of a raised exception, as well as an exception or - /// signal handler. - /// - /// If this field is true, then the stack trace was captured as part - /// of creating an unrelated event. For example, a thread other than the - /// crashing thread, or a stack trace computed as a result of an external kill - /// signal. - bool? snapshot; - - @internal - String? baseAddr; - - @internal - String? buildId; - - @internal - final Map? unknown; - - /// Deserializes a [SentryStackTrace] from JSON [Map]. - factory SentryStackTrace.fromJson(Map data) { - final json = AccessAwareMap(data); - final framesJson = json['frames'] as List?; - return SentryStackTrace( - frames: framesJson != null - ? framesJson - .map((frameJson) => SentryStackFrame.fromJson(frameJson)) - .toList() - : [], - registers: json['registers'], - lang: json['lang'], - snapshot: json['snapshot'], - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - if (_frames?.isNotEmpty ?? false) - 'frames': - _frames?.map((frame) => frame.toJson()).toList(growable: false), - if (_registers?.isNotEmpty ?? false) 'registers': _registers, - if (lang != null) 'lang': lang, - if (snapshot != null) 'snapshot': snapshot, - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryStackTrace copyWith({ - List? frames, - Map? registers, - String? lang, - bool? snapshot, - }) => - SentryStackTrace( - frames: frames ?? this.frames, - registers: registers ?? this.registers, - lang: lang ?? this.lang, - snapshot: snapshot ?? this.snapshot, - unknown: unknown, - baseAddr: baseAddr, - buildId: buildId, - ); -} diff --git a/dart/lib/src/protocol/sentry_thread.dart b/dart/lib/src/protocol/sentry_thread.dart deleted file mode 100644 index 828ab969a4..0000000000 --- a/dart/lib/src/protocol/sentry_thread.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'sentry_stack_trace.dart'; -import 'access_aware_map.dart'; - -/// The Threads Interface specifies threads that were running at the time an -/// event happened. These threads can also contain stack traces. -/// See https://develop.sentry.dev/sdk/event-payloads/threads/ -class SentryThread { - SentryThread({ - this.id, - this.name, - this.crashed, - this.current, - this.stacktrace, - this.unknown, - }); - - factory SentryThread.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryThread( - id: json['id'] as int?, - name: json['name'] as String?, - crashed: json['crashed'] as bool?, - current: json['current'] as bool?, - stacktrace: - json['stacktrace'] == null ? null : SentryStackTrace.fromJson(json), - unknown: json.notAccessed(), - ); - } - - /// The Id of the thread. - int? id; - - /// The name of the thread. - /// On Dart platforms where Isolates are available, this can be set to - /// [Isolate.debugName](https://api.flutter.dev/flutter/dart-isolate/Isolate/debugName.html) - String? name; - - /// Whether the crash happened on this thread. - bool? crashed; - - /// An optional flag to indicate that the thread was in the foreground. - bool? current; - - /// Stack trace. - /// See https://develop.sentry.dev/sdk/event-payloads/stacktrace/ - SentryStackTrace? stacktrace; - - @internal - final Map? unknown; - - Map toJson() { - final stacktrace = this.stacktrace; - return { - ...?unknown, - if (id != null) 'id': id, - if (name != null) 'name': name, - if (crashed != null) 'crashed': crashed, - if (current != null) 'current': current, - if (stacktrace != null) 'stacktrace': stacktrace.toJson(), - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryThread copyWith({ - int? id, - String? name, - bool? crashed, - bool? current, - SentryStackTrace? stacktrace, - }) { - return SentryThread( - id: id ?? this.id, - name: name ?? this.name, - crashed: crashed ?? this.crashed, - current: current ?? this.current, - stacktrace: stacktrace ?? this.stacktrace, - unknown: unknown, - ); - } -} diff --git a/dart/lib/src/protocol/sentry_trace_context.dart b/dart/lib/src/protocol/sentry_trace_context.dart deleted file mode 100644 index 96f806ba39..0000000000 --- a/dart/lib/src/protocol/sentry_trace_context.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../../sentry.dart'; -import '../propagation_context.dart'; -import '../protocol.dart'; -import 'access_aware_map.dart'; - -class SentryTraceContext { - static const String type = 'trace'; - - /// Determines which trace the Span belongs to - final SentryId traceId; - - /// Span id - final SpanId spanId; - - /// Id of a parent span - SpanId? parentSpanId; - - /// Replay associated with this trace. - SentryId? replayId; - - /// Whether the span is sampled or not - bool? sampled; - - /// Short code identifying the type of operation the span is measuring - String operation; - - /// Longer description of the span's operation, which uniquely identifies the span but is - /// consistent across instances of the span. - String? description; - - /// The Span status - SpanStatus? status; - - /// The origin of the span indicates what created the span. - /// - /// @note Gets set by the SDK. It is not expected to be set manually by users. - /// - /// @see - String? origin; - - Map? data; - - @internal - final Map? unknown; - - factory SentryTraceContext.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryTraceContext( - operation: json['op'] as String, - spanId: SpanId.fromId(json['span_id'] as String), - parentSpanId: json['parent_span_id'] == null - ? null - : SpanId.fromId(json['parent_span_id'] as String), - traceId: SentryId.fromId(json['trace_id'] as String), - replayId: json['replay_id'] == null - ? null - : SentryId.fromId(json['replay_id'] as String), - description: json['description'] as String?, - status: json['status'] == null - ? null - : SpanStatus.fromString(json['status'] as String), - sampled: true, - origin: json['origin'] == null ? null : json['origin'] as String?, - data: json['data'] == null ? null : json['data'] as Map, - unknown: json.notAccessed(), - ); - } - - /// Item encoded as JSON - Map toJson() { - return { - ...?unknown, - 'span_id': spanId.toString(), - 'trace_id': traceId.toString(), - 'op': operation, - if (parentSpanId != null) 'parent_span_id': parentSpanId!.toString(), - if (replayId != null) 'replay_id': replayId!.toString(), - if (description != null) 'description': description, - if (status != null) 'status': status!.toString(), - if (origin != null) 'origin': origin, - if (data != null) 'data': data, - }; - } - - @Deprecated('Will be removed in a future version.') - SentryTraceContext clone() => SentryTraceContext( - operation: operation, - traceId: traceId, - spanId: spanId, - description: description, - status: status, - parentSpanId: parentSpanId, - sampled: sampled, - origin: origin, - unknown: unknown, - replayId: replayId, - data: data, - ); - - SentryTraceContext({ - SentryId? traceId, - SpanId? spanId, - this.parentSpanId, - this.sampled, - required this.operation, - this.description, - this.status, - this.origin, - this.unknown, - this.replayId, - this.data, - }) : traceId = traceId ?? SentryId.newId(), - spanId = spanId ?? SpanId.newId(); - - @internal - factory SentryTraceContext.fromPropagationContext( - PropagationContext propagationContext) { - return SentryTraceContext( - traceId: propagationContext.traceId, - spanId: SpanId.newId(), - operation: 'default', - sampled: propagationContext.sampled, - replayId: propagationContext.baggage?.getReplayId(), - ); - } -} diff --git a/dart/lib/src/protocol/sentry_trace_header.dart b/dart/lib/src/protocol/sentry_trace_header.dart deleted file mode 100644 index a4aebe3416..0000000000 --- a/dart/lib/src/protocol/sentry_trace_header.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../invalid_sentry_trace_header_exception.dart'; -import '../protocol.dart'; - -/// Represents HTTP header "sentry-trace". -@immutable -class SentryTraceHeader { - static const _traceHeader = 'sentry-trace'; - - final SentryId traceId; - final SpanId spanId; - final bool? sampled; - - String get name => _traceHeader; - - String get value { - if (sampled != null) { - final sampled = this.sampled! ? '1' : '0'; - return '$traceId-$spanId-$sampled'; - } else { - return '$traceId-$spanId'; - } - } - - SentryTraceHeader( - this.traceId, - this.spanId, { - this.sampled, - }); - - factory SentryTraceHeader.fromTraceHeader(String header) { - final parts = header.split('-'); - bool? sampled; - - if (parts.length < 2) { - throw InvalidSentryTraceHeaderException('Header: $header is invalid.'); - } else if (parts.length == 3) { - sampled = '1' == parts[2]; - } - - final traceId = SentryId.fromId(parts[0]); - final spanId = SpanId.fromId(parts[1]); - - return SentryTraceHeader( - traceId, - spanId, - sampled: sampled, - ); - } -} diff --git a/dart/lib/src/protocol/sentry_transaction.dart b/dart/lib/src/protocol/sentry_transaction.dart deleted file mode 100644 index 0dff8c151e..0000000000 --- a/dart/lib/src/protocol/sentry_transaction.dart +++ /dev/null @@ -1,155 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../protocol.dart'; -import '../sentry_measurement.dart'; -import '../sentry_tracer.dart'; -import '../utils.dart'; - -class SentryTransaction extends SentryEvent { - late final DateTime startTimestamp; - static const String _type = 'transaction'; - late final List spans; - @internal - final SentryTracer tracer; - late final Map measurements; - late final SentryTransactionInfo? transactionInfo; - - SentryTransaction( - this.tracer, { - super.eventId, - DateTime? timestamp, - super.platform, - super.serverName, - super.release, - super.dist, - super.environment, - String? transaction, - dynamic throwable, - Map? tags, - @Deprecated( - 'Additional Data is deprecated in favor of structured [Contexts] and should be avoided when possible') - Map? extra, - super.user, - super.contexts, - super.breadcrumbs, - super.sdk, - super.request, - String? type, - Map? measurements, - SentryTransactionInfo? transactionInfo, - }) : super( - timestamp: timestamp ?? tracer.endTimestamp, - transaction: transaction ?? tracer.name, - throwable: throwable ?? tracer.throwable, - tags: tags ?? tracer.tags, - // ignore: deprecated_member_use_from_same_package - extra: extra ?? tracer.data, - type: _type, - ) { - startTimestamp = tracer.startTimestamp; - - final spanContext = tracer.context; - spans = tracer.children; - this.measurements = measurements ?? {}; - - final data = extra ?? tracer.data; - contexts.trace = spanContext.toTraceContext( - sampled: tracer.samplingDecision?.sampled, - status: tracer.status, - data: data.isEmpty ? null : data, - ); - - this.transactionInfo = transactionInfo ?? - SentryTransactionInfo(tracer.transactionNameSource.name); - } - - @override - Map toJson() { - final json = super.toJson(); - - if (spans.isNotEmpty) { - json['spans'] = spans.map((e) => e.toJson()).toList(growable: false); - } - json['start_timestamp'] = - formatDateAsIso8601WithMillisPrecision(startTimestamp); - - if (measurements.isNotEmpty) { - final map = {}; - for (final item in measurements.entries) { - map[item.key] = item.value.toJson(); - } - json['measurements'] = map; - } - - final transactionInfo = this.transactionInfo; - if (transactionInfo != null) { - json['transaction_info'] = transactionInfo.toJson(); - } - - return json; - } - - bool get finished => timestamp != null; - - bool get sampled => contexts.trace?.sampled == true; - - @Deprecated('Assign values directly to the instance.') - @override - SentryTransaction copyWith({ - SentryId? eventId, - DateTime? timestamp, - String? platform, - String? logger, - String? serverName, - String? release, - String? dist, - String? environment, - Map? modules, - SentryMessage? message, - String? transaction, - dynamic throwable, - SentryLevel? level, - String? culprit, - Map? tags, - @Deprecated( - 'Additional Data is deprecated in favor of structured [Contexts] and should be avoided when possible') - Map? extra, - List? fingerprint, - SentryUser? user, - Contexts? contexts, - List? breadcrumbs, - SdkVersion? sdk, - SentryRequest? request, - DebugMeta? debugMeta, - List? exceptions, - List? threads, - String? type, - Map? measurements, - SentryTransactionInfo? transactionInfo, - }) => - SentryTransaction( - tracer, - eventId: eventId ?? this.eventId, - timestamp: timestamp ?? this.timestamp, - platform: platform ?? this.platform, - serverName: serverName ?? this.serverName, - release: release ?? this.release, - dist: dist ?? this.dist, - environment: environment ?? this.environment, - transaction: transaction ?? this.transaction, - throwable: throwable ?? this.throwable, - tags: (tags != null ? Map.from(tags) : null) ?? this.tags, - // ignore: deprecated_member_use_from_same_package - extra: (extra != null ? Map.from(extra) : null) ?? this.extra, - user: user ?? this.user, - contexts: contexts ?? this.contexts, - breadcrumbs: (breadcrumbs != null ? List.from(breadcrumbs) : null) ?? - this.breadcrumbs, - sdk: sdk ?? this.sdk, - request: request ?? this.request, - type: type ?? this.type, - measurements: (measurements != null ? Map.from(measurements) : null) ?? - this.measurements, - transactionInfo: transactionInfo ?? this.transactionInfo, - ); -} diff --git a/dart/lib/src/protocol/sentry_transaction_info.dart b/dart/lib/src/protocol/sentry_transaction_info.dart deleted file mode 100644 index a70e536375..0000000000 --- a/dart/lib/src/protocol/sentry_transaction_info.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'access_aware_map.dart'; - -class SentryTransactionInfo { - SentryTransactionInfo(this.source, {this.unknown}); - - final String source; - - @internal - final Map? unknown; - - Map toJson() { - return { - ...?unknown, - 'source': source, - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryTransactionInfo copyWith({ - String? source, - }) { - return SentryTransactionInfo( - source ?? this.source, - unknown: unknown, - ); - } - - factory SentryTransactionInfo.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryTransactionInfo( - json['source'], - unknown: json.notAccessed(), - ); - } -} diff --git a/dart/lib/src/protocol/sentry_transaction_name_source.dart b/dart/lib/src/protocol/sentry_transaction_name_source.dart deleted file mode 100644 index 278d1757a5..0000000000 --- a/dart/lib/src/protocol/sentry_transaction_name_source.dart +++ /dev/null @@ -1,19 +0,0 @@ -enum SentryTransactionNameSource { - /// User-defined name - custom, - - /// Raw URL, potentially containing identifiers. - url, - - /// Parametrized URL / route - route, - - /// Name of the view handling the request. - view, - - /// Named after a software component, such as a function or class name. - component, - - /// Name of a background task - task, -} diff --git a/dart/lib/src/protocol/sentry_user.dart b/dart/lib/src/protocol/sentry_user.dart deleted file mode 100644 index 2b6f5f4f95..0000000000 --- a/dart/lib/src/protocol/sentry_user.dart +++ /dev/null @@ -1,164 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../../sentry.dart'; -import 'access_aware_map.dart'; - -/// Describes the current user associated with the application, such as the -/// currently signed in user. -/// -/// The user can be specified globally in the [Scope.user] field, -/// or per event in the [SentryEvent.user] field. -/// -/// You should provide at least one of [id], [email], [ipAddress], [username] -/// for Sentry to be able to tell you how many users are affected by one -/// issue, for example. Sending a user that has none of these attributes and -/// only custom attributes is valid, but not as useful. -/// -/// Conforms to the User Interface contract for Sentry -/// https://develop.sentry.dev/sdk/event-payloads/user/ -/// -/// The outgoing JSON representation is: -/// -/// ``` -/// "user": { -/// "id": "unique_id", -/// "username": "my_user", -/// "email": "foo@example.com", -/// "ip_address": "127.0.0.1", -/// } -/// ``` -class SentryUser { - /// You should provide at least one of [id], [email], [ipAddress], [username] - /// for Sentry to be able to tell you how many users are affected by one - /// issue, for example. Sending a user that has none of these attributes and - /// only custom attributes is valid, but not as useful. - SentryUser({ - this.id, - this.username, - this.email, - this.ipAddress, - this.geo, - this.name, - Map? data, - @Deprecated('Will be removed in v8. Use [data] instead') - Map? extras, - this.unknown, - }) : assert(id != null || - username != null || - email != null || - ipAddress != null), - data = data == null ? null : Map.from(data), - // ignore: deprecated_member_use_from_same_package - extras = extras == null ? null : Map.from(extras); - - /// A unique identifier of the user. - String? id; - - /// The username of the user. - String? username; - - /// The email address of the user. - String? email; - - /// The IP of the user. - String? ipAddress; - - /// Any other user context information that may be helpful. - /// - /// These keys are stored as extra information but not specifically processed - /// by Sentry. - Map? data; - - @Deprecated('Will be removed in v8. Use [data] instead') - Map? extras; - - /// Approximate geographical location of the end user or device. - /// - /// The geolocation is automatically inferred by Sentry.io if the [ipAddress] is set. - /// Sentry however doesn't collect the [ipAddress] automatically because it is PII. - /// The geo location will currently not be synced to the native layer, if available. - // See https://github.com/getsentry/sentry-dart/issues/1065 - SentryGeo? geo; - - /// Human readable name of the user. - String? name; - - @internal - final Map? unknown; - - /// Deserializes a [SentryUser] from JSON [Map]. - factory SentryUser.fromJson(Map jsonData) { - final json = AccessAwareMap(jsonData); - - var extras = json['extras']; - if (extras != null) { - extras = Map.from(extras); - } - - var data = json['data']; - if (data != null) { - data = Map.from(data); - } - - SentryGeo? geo; - final geoJson = json['geo']; - if (geoJson != null) { - geo = SentryGeo.fromJson(Map.from(geoJson)); - } - return SentryUser( - id: json['id'], - username: json['username'], - email: json['email'], - ipAddress: json['ip_address'], - data: data, - geo: geo, - name: json['name'], - // ignore: deprecated_member_use_from_same_package - extras: extras, - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - final geoJson = geo?.toJson(); - return { - ...?unknown, - if (id != null) 'id': id, - if (username != null) 'username': username, - if (email != null) 'email': email, - if (ipAddress != null) 'ip_address': ipAddress, - if (data?.isNotEmpty ?? false) 'data': data, - // ignore: deprecated_member_use_from_same_package - if (extras?.isNotEmpty ?? false) 'extras': extras, - if (name != null) 'name': name, - if (geoJson != null && geoJson.isNotEmpty) 'geo': geoJson, - }; - } - - @Deprecated('Assign values directly to the instance.') - SentryUser copyWith({ - String? id, - String? username, - String? email, - String? ipAddress, - @Deprecated('Will be removed in v8. Use [data] instead') - Map? extras, - String? name, - SentryGeo? geo, - Map? data, - }) { - return SentryUser( - id: id ?? this.id, - username: username ?? this.username, - email: email ?? this.email, - ipAddress: ipAddress ?? this.ipAddress, - data: data ?? this.data, - // ignore: deprecated_member_use_from_same_package - extras: extras ?? this.extras, - geo: geo ?? this.geo, - name: name ?? this.name, - unknown: unknown, - ); - } -} diff --git a/dart/lib/src/protocol/sentry_view_hierarchy.dart b/dart/lib/src/protocol/sentry_view_hierarchy.dart deleted file mode 100644 index f97410f7ac..0000000000 --- a/dart/lib/src/protocol/sentry_view_hierarchy.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'sentry_view_hierarchy_element.dart'; - -@immutable -class SentryViewHierarchy { - SentryViewHierarchy(this.renderingSystem); - - final String renderingSystem; - final List windows = []; - - /// Header encoded as JSON - Map toJson() { - return { - 'rendering_system': renderingSystem, - if (windows.isNotEmpty) - 'windows': windows.map((e) => e.toJson()).toList(growable: false), - }; - } -} diff --git a/dart/lib/src/protocol/sentry_view_hierarchy_element.dart b/dart/lib/src/protocol/sentry_view_hierarchy_element.dart deleted file mode 100644 index 5a4ae777e4..0000000000 --- a/dart/lib/src/protocol/sentry_view_hierarchy_element.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:meta/meta.dart'; - -@immutable -class SentryViewHierarchyElement { - SentryViewHierarchyElement( - this.type, { - this.depth, - this.identifier, - this.width, - this.height, - this.x, - this.y, - this.z, - this.visible, - this.alpha, - this.extra, - }); - - final String type; - final int? depth; - final String? identifier; - final List children = []; - final double? width; - final double? height; - final double? x; - final double? y; - final double? z; - final bool? visible; - final double? alpha; - final Map? extra; - - /// Header encoded as JSON - Map toJson() { - final jsonMap = { - 'type': type, - if (depth != null) 'depth': depth, - if (identifier != null) 'identifier': identifier, - if (width != null) 'width': width, - if (height != null) 'height': height, - if (x != null) 'x': x, - if (y != null) 'y': y, - if (z != null) 'z': z, - if (visible != null) 'visible': visible, - if (alpha != null) 'alpha': alpha, - if (children.isNotEmpty) - 'children': children.map((e) => e.toJson()).toList(growable: false), - }; - - if (extra?.isNotEmpty ?? false) { - jsonMap.addAll(extra!); - } - - return jsonMap; - } -} diff --git a/dart/lib/src/protocol/span_id.dart b/dart/lib/src/protocol/span_id.dart deleted file mode 100644 index c72ba37957..0000000000 --- a/dart/lib/src/protocol/span_id.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:uuid/uuid.dart'; - -/// The length is exactly 16 characters. -/// Dashes are not allowed. Has to be lowercase. -@immutable -class SpanId { - final String _id; - - static final Uuid _uuidGenerator = Uuid(); - - SpanId._internal({String? id}) - : _id = id?.replaceAll('-', '') ?? - _uuidGenerator.v4().replaceAll('-', '').substring(0, 16); - - /// Generates a new SpanId - SpanId.newId() : this._internal(); - - /// Generates a SpanId with the given UUID - SpanId.fromId(String id) : this._internal(id: id); - - /// SpanId with an empty UUID - const SpanId.empty() : _id = '0000000000000000'; - - @override - String toString() => _id; - - @override - int get hashCode => _id.hashCode; - - @override - bool operator ==(o) { - if (o is SpanId) { - return o._id == _id; - } - return false; - } -} diff --git a/dart/lib/src/protocol/span_status.dart b/dart/lib/src/protocol/span_status.dart deleted file mode 100644 index 007fe1cfd9..0000000000 --- a/dart/lib/src/protocol/span_status.dart +++ /dev/null @@ -1,145 +0,0 @@ -/// The Span statuses -class SpanStatus { - const SpanStatus._( - this._value, - this._minHttpStatusCode, { - int? maxHttpStatusCode, - }) : _maxHttpStatusCode = maxHttpStatusCode ?? _minHttpStatusCode; - - /// Not an error, returned on success. - const SpanStatus.ok() : this._('ok', 200, maxHttpStatusCode: 299); - - /// The operation was cancelled, typically by the caller. - const SpanStatus.cancelled() : this._('cancelled', 499); - - /// Some invariants expected by the underlying system have been broken. - /// This code is reserved for serious errors. - const SpanStatus.internalError() : this._('internal_error', 500); - - /// An unknown error raised by APIs that don't return enough error information. - const SpanStatus.unknown() : this._('unknown', 500); - - /// An unknown error raised by APIs that don't return enough error information. - const SpanStatus.unknownError() : this._('unknown_error', 500); - - /// The client specified an invalid argument. - const SpanStatus.invalidArgument() : this._('invalid_argument', 400); - - /// The deadline expired before the operation could succeed. - const SpanStatus.deadlineExceeded() : this._('deadline_exceeded', 504); - - /// Content was not found or request was denied for an entire class of users. - const SpanStatus.notFound() : this._('not_found', 404); - - /// The entity attempted to be created already exists - const SpanStatus.alreadyExists() : this._('already_exists', 409); - - /// The caller doesn't have permission to execute the specified operation. - const SpanStatus.permissionDenied() : this._('permission_denied', 403); - - /// The resource has been exhausted e.g. per-user quota exhausted, file system out of space. - const SpanStatus.resourceExhausted() : this._('resource_exhausted', 429); - - /// The client shouldn't retry until the system state has been explicitly handled. - const SpanStatus.failedPrecondition() : this._('failed_precondition', 400); - - /// The operation was aborted. - const SpanStatus.aborted() : this._('aborted', 409); - - /// The operation was attempted past the valid range e.g. seeking past the end of a file. - const SpanStatus.outOfRange() : this._('out_of_range', 400); - - /// The operation is not implemented or is not supported/enabled for this operation. - const SpanStatus.unimplemented() : this._('unimplemented', 501); - - /// The service is currently available e.g. as a transient condition. - const SpanStatus.unavailable() : this._('unavailable', 503); - - /// Unrecoverable data loss or corruption. - const SpanStatus.dataLoss() : this._('data_loss', 500); - - /// The requester doesn't have valid authentication credentials for the operation. - const SpanStatus.unauthenticated() : this._('unauthenticated', 401); - - final String _value; - final int _minHttpStatusCode; - final int _maxHttpStatusCode; - - @override - String toString() => _value; - - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(o) { - if (o is SpanStatus) { - return o._value == _value && - o._minHttpStatusCode == _minHttpStatusCode && - o._maxHttpStatusCode == _maxHttpStatusCode; - } - return false; - } - - /// Creates SpanStatus from HTTP status code. - factory SpanStatus.fromHttpStatusCode( - int httpStatusCode, { - SpanStatus? fallback, - }) { - var status = SpanStatus.ok(); - if (_matches(status, httpStatusCode)) { - return status; - } - status = SpanStatus.cancelled(); - if (_matches(status, httpStatusCode)) { - return status; - } - status = SpanStatus.unknown(); - if (_matches(status, httpStatusCode)) { - return status; - } - status = SpanStatus.invalidArgument(); - if (_matches(status, httpStatusCode)) { - return status; - } - status = SpanStatus.deadlineExceeded(); - if (_matches(status, httpStatusCode)) { - return status; - } - status = SpanStatus.notFound(); - if (_matches(status, httpStatusCode)) { - return status; - } - status = SpanStatus.alreadyExists(); - if (_matches(status, httpStatusCode)) { - return status; - } - status = SpanStatus.permissionDenied(); - if (_matches(status, httpStatusCode)) { - return status; - } - status = SpanStatus.resourceExhausted(); - if (_matches(status, httpStatusCode)) { - return status; - } - status = SpanStatus.unimplemented(); - if (_matches(status, httpStatusCode)) { - return status; - } - status = SpanStatus.unavailable(); - if (_matches(status, httpStatusCode)) { - return status; - } - status = SpanStatus.unauthenticated(); - if (_matches(status, httpStatusCode)) { - return status; - } - return fallback ?? SpanStatus.unknownError(); - } - - /// Creates SpanStatus from a String. - factory SpanStatus.fromString(String value) => SpanStatus._(value, 0); - - static bool _matches(SpanStatus status, int code) => - code >= status._minHttpStatusCode && code <= status._maxHttpStatusCode; -} diff --git a/dart/lib/src/recursive_exception_cause_extractor.dart b/dart/lib/src/recursive_exception_cause_extractor.dart deleted file mode 100644 index f88b5f8fdc..0000000000 --- a/dart/lib/src/recursive_exception_cause_extractor.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'sentry_options.dart'; -import 'throwable_mechanism.dart'; -import 'exception_cause.dart'; -import 'protocol.dart'; -import 'package:meta/meta.dart'; - -/// Extracts inner exceptions recursively -@internal -class RecursiveExceptionCauseExtractor { - RecursiveExceptionCauseExtractor(this._options); - - final SentryOptions _options; - - List flatten(dynamic exception, dynamic stackTrace) { - final allExceptionCauses = []; - final circularityDetector = {}; - - var currentException = exception; - ExceptionCause? currentExceptionCause = ExceptionCause( - exception, - stackTrace, - ); - - while (currentException != null && - currentExceptionCause != null && - circularityDetector.add(currentException)) { - allExceptionCauses.add(currentExceptionCause); - - final extractionSourceSource = currentException is ThrowableMechanism - ? currentException.throwable - : currentException; - - final extractor = - _options.exceptionCauseExtractor(extractionSourceSource.runtimeType); - - try { - currentExceptionCause = extractor?.cause(extractionSourceSource); - currentException = currentExceptionCause?.exception; - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'An exception occurred while extracting exception cause', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - break; - } - } - return allExceptionCauses; - } -} diff --git a/dart/lib/src/run_zoned_guarded_integration.dart b/dart/lib/src/run_zoned_guarded_integration.dart deleted file mode 100644 index 8ea770653f..0000000000 --- a/dart/lib/src/run_zoned_guarded_integration.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'dart:async'; - -import 'sentry_run_zoned_guarded.dart'; -import '../sentry.dart'; - -/// Called inside of `runZonedGuarded` -typedef RunZonedGuardedRunner = Future Function(); - -/// Caught exception and stacktrace in `runZonedGuarded` -typedef RunZonedGuardedOnError = FutureOr Function(Object, StackTrace); - -/// Integration that runs runner function within `runZonedGuarded` and capture -/// errors on the `runZonedGuarded` error handler. -/// See https://api.dart.dev/stable/dart-async/runZonedGuarded.html -/// -/// This integration also records calls to `print()` as Breadcrumbs. -/// This can be configured with [SentryOptions.enablePrintBreadcrumbs] -class RunZonedGuardedIntegration extends Integration { - RunZonedGuardedIntegration(this._runner, this._onError); - - final RunZonedGuardedRunner _runner; - final RunZonedGuardedOnError? _onError; - - @override - Future call(Hub hub, SentryOptions options) { - final completer = Completer(); - - SentryRunZonedGuarded.sentryRunZonedGuarded(hub, () async { - try { - await _runner(); - } finally { - completer.complete(); - } - }, _onError); - - options.sdk.addIntegration('runZonedGuardedIntegration'); - - return completer.future; - } -} diff --git a/dart/lib/src/runtime_checker.dart b/dart/lib/src/runtime_checker.dart deleted file mode 100644 index d458448b69..0000000000 --- a/dart/lib/src/runtime_checker.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'dart:async'; - -import 'utils/stacktrace_utils.dart'; - -/// Helper to check in which environment the library is running. -/// The environment checks (release/debug/profile) are mutually exclusive. -class RuntimeChecker { - RuntimeChecker({ - bool? isRootZone, - }) : isRootZone = isRootZone ?? Zone.current == Zone.root; - - /// Check if running in release/production environment - bool isReleaseMode() { - return const bool.fromEnvironment('dart.vm.product', defaultValue: false); - } - - /// Check if running in debug environment - bool isDebugMode() { - return !isReleaseMode() && !isProfileMode(); - } - - /// Check if running in profile environment - bool isProfileMode() { - return const bool.fromEnvironment('dart.vm.profile', defaultValue: false); - } - - /// Check if the Dart code is obfuscated. - bool isAppObfuscated() { - // In non-obfuscated builds, this will return "RuntimeChecker" - // In obfuscated builds, this will return something like "a" or other short identifier - // Note: Flutter Web production builds will always be minified / "obfuscated". - final typeName = runtimeType.toString(); - return !typeName.contains('RuntimeChecker'); - } - - /// Check if the current build has been built with --split-debug-info - bool isSplitDebugInfoBuild() { - final str = StackTrace.current.toString(); - return buildIdRegex.hasMatch(str) || absRegex.hasMatch(str); - } - - final bool isRootZone; - - String get compileMode { - return isReleaseMode() - ? 'release' - : isDebugMode() - ? 'debug' - : 'profile'; - } -} diff --git a/dart/lib/src/scope.dart b/dart/lib/src/scope.dart deleted file mode 100644 index fef52b5d9f..0000000000 --- a/dart/lib/src/scope.dart +++ /dev/null @@ -1,474 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; - -import 'package:meta/meta.dart'; - -import 'event_processor.dart'; -import 'event_processor/run_event_processors.dart'; -import 'hint.dart'; -import 'propagation_context.dart'; -import 'protocol.dart'; -import 'scope_observer.dart'; -import 'sentry_attachment/sentry_attachment.dart'; -import 'sentry_options.dart'; -import 'sentry_span_interface.dart'; -import 'sentry_tracer.dart'; - -typedef _OnScopeObserver = Future Function(ScopeObserver observer); - -/// Scope data to be sent with the event -class Scope { - /// How important this event is. - SentryLevel? level; - - String? _transaction; - - /// The name of the transaction which generated this event, - /// for example, the route name: `"/users//"`. - String? get transaction { - return ((span is SentryTracer) ? (span as SentryTracer?)?.name : null) ?? - _transaction; - } - - set transaction(String? transaction) { - _transaction = transaction; - - if (_transaction != null && span != null) { - final currentTransaction = - (span is SentryTracer) ? (span as SentryTracer?) : null; - currentTransaction?.name = _transaction!; - } - } - - /// Returns active transaction or null if there is no active transaction. - ISentrySpan? span; - - /// The propagation context for connecting errors and spans to traces. - /// There should always be a propagation context available at all times. - /// - /// Default behaviour of trace generation in Flutter: - /// - /// If `SentryNavigatorObserver` is available: - /// - new trace on navigation for all platforms - /// - /// if `SentryNavigatorObserver` is not available: - /// - Mobile: traces will be cycled with the background/foreground hooks, similar to how sessions are defined in mobile - /// - Web: traces will stick until the next refresh (this might change in the future) - @internal - PropagationContext propagationContext = PropagationContext(); - - SentryUser? _user; - - /// Get the current user. - SentryUser? get user => _user; - - void _setUserSync(SentryUser? user) { - _user = user; - } - - /// Set the current user. - Future setUser(SentryUser? user) async { - _setUserSync(user); - await _callScopeObservers( - (scopeObserver) async => await scopeObserver.setUser(user)); - } - - List _fingerprint = []; - - /// Used to deduplicate events by grouping ones with the same fingerprint - /// together. - /// - /// Example: - /// - /// // A completely custom fingerprint: - /// var custom = ['foo', 'bar', 'baz']; - List get fingerprint => List.unmodifiable(_fingerprint); - - set fingerprint(List fingerprint) { - _fingerprint = List.from(fingerprint); - } - - /// List of breadcrumbs for this scope. - final Queue _breadcrumbs = Queue(); - - /// Unmodifiable List of breadcrumbs - /// See also: - /// * https://docs.sentry.io/enriching-error-data/breadcrumbs/?platform=javascript - List get breadcrumbs => List.unmodifiable(_breadcrumbs); - - final Map _tags = {}; - - /// Name/value pairs that events can be searched by. - Map get tags => Map.unmodifiable(_tags); - - final Map _extra = {}; - - /// Arbitrary name/value pairs attached to the scope. - /// - /// Sentry.io docs do not talk about restrictions on the values, other than - /// they must be JSON-serializable. - Map get extra => Map.unmodifiable(_extra); - - /// Active replay recording. - @internal - SentryId? get replayId => _replayId; - @internal - set replayId(SentryId? value) => _replayId = value; - SentryId? _replayId; - - final Contexts _contexts = Contexts(); - - /// Unmodifiable map of the scope contexts key/value - /// See also: - /// * https://docs.sentry.io/platforms/java/enriching-events/context/ - Map get contexts => Map.unmodifiable(_contexts); - - void _setContextsSync(String key, dynamic value) { - // if it's a List, it should not be a List because it can't - // be wrapped by the value object since it's a special property for having - // multiple runtimes and it has a dedicated property within the Contexts class. - _contexts[key] = (value is num || - value is bool || - value is String || - (value is List && - (value is! List && - key != SentryRuntime.listType))) - ? {'value': value} - : value; - } - - /// add an entry to the Scope's contexts - Future setContexts(String key, dynamic value) async { - _setContextsSync(key, value); - await _callScopeObservers( - (scopeObserver) async => await scopeObserver.setContexts(key, value)); - } - - /// Removes a value from the Scope's contexts - Future removeContexts(String key) async { - _contexts.remove(key); - - await _callScopeObservers( - (scopeObserver) async => await scopeObserver.removeContexts(key)); - } - - /// Scope's event processor list - /// - /// Scope's event processors are executed before the global Event processors - final List _eventProcessors = []; - - List get eventProcessors => - List.unmodifiable(_eventProcessors); - - final SentryOptions _options; - bool _enableScopeSync = true; - - final List _attachments = []; - - List get attachments => List.unmodifiable(_attachments); - - Scope(this._options); - - Breadcrumb? _addBreadCrumbSync(Breadcrumb breadcrumb, Hint hint) { - // bail out if maxBreadcrumbs is zero - if (_options.maxBreadcrumbs == 0) { - return null; - } - - Breadcrumb? processedBreadcrumb = breadcrumb; - // run before breadcrumb callback if set - if (_options.beforeBreadcrumb != null) { - try { - processedBreadcrumb = _options.beforeBreadcrumb!( - processedBreadcrumb, - hint, - ); - if (processedBreadcrumb == null) { - _options.log( - SentryLevel.info, - 'Breadcrumb was dropped by beforeBreadcrumb', - ); - return null; - } - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'The BeforeBreadcrumb callback threw an exception', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } - } - if (processedBreadcrumb != null) { - // remove first item if list is full - if (_breadcrumbs.length >= _options.maxBreadcrumbs && - _breadcrumbs.isNotEmpty) { - _breadcrumbs.removeFirst(); - } - _breadcrumbs.add(processedBreadcrumb); - } - return processedBreadcrumb; - } - - /// Adds a breadcrumb to the breadcrumbs queue - Future addBreadcrumb(Breadcrumb breadcrumb, {Hint? hint}) async { - final addedBreadcrumb = _addBreadCrumbSync(breadcrumb, hint ?? Hint()); - if (addedBreadcrumb != null) { - await _callScopeObservers((scopeObserver) async => - await scopeObserver.addBreadcrumb(addedBreadcrumb)); - } - } - - void addAttachment(SentryAttachment attachment) { - _attachments.add(attachment); - } - - void clearAttachments() { - _attachments.clear(); - } - - void _clearBreadcrumbsSync() { - _breadcrumbs.clear(); - } - - /// Clear all the breadcrumbs - Future clearBreadcrumbs() async { - _clearBreadcrumbsSync(); - await _callScopeObservers( - (scopeObserver) async => await scopeObserver.clearBreadcrumbs()); - } - - /// Adds an event processor - void addEventProcessor(EventProcessor eventProcessor) { - _eventProcessors.add(eventProcessor); - } - - /// Resets the Scope to its default state - Future clear() async { - clearAttachments(); - level = null; - span = null; - _transaction = null; - _fingerprint = []; - _tags.clear(); - _extra.clear(); - _eventProcessors.clear(); - _replayId = null; - propagationContext = PropagationContext(); - - _clearBreadcrumbsSync(); - _setUserSync(null); - - await clearBreadcrumbs(); - await setUser(null); - } - - void _setTagSync(String key, String value) { - _tags[key] = value; - } - - /// Sets a tag to the Scope - Future setTag(String key, String value) async { - _setTagSync(key, value); - await _callScopeObservers( - (scopeObserver) async => await scopeObserver.setTag(key, value)); - } - - /// Removes a tag from the Scope - Future removeTag(String key) async { - _tags.remove(key); - await _callScopeObservers( - (scopeObserver) async => await scopeObserver.removeTag(key)); - } - - void _setExtraSync(String key, dynamic value) { - _extra[key] = value; - } - - /// Sets an extra to the Scope - @Deprecated( - 'Use Contexts instead. Additional data is deprecated in favor of structured Contexts and should be avoided when possible') - Future setExtra(String key, dynamic value) async { - _setExtraSync(key, value); - await _callScopeObservers( - (scopeObserver) async => await scopeObserver.setExtra(key, value)); - } - - /// Removes an extra from the Scope - @Deprecated( - 'Use Contexts instead. Additional data is deprecated in favor of structured Contexts and should be avoided when possible') - Future removeExtra(String key) async { - _extra.remove(key); - await _callScopeObservers( - (scopeObserver) async => await scopeObserver.removeExtra(key)); - } - - Future applyToEvent( - SentryEvent event, - Hint hint, - ) async { - event - ..transaction = event.transaction ?? transaction - ..user = _mergeUsers(user, event.user) - ..tags = tags.isNotEmpty ? _mergeEventTags(event) : event.tags; - - if (event.type != 'feedback') { - event.breadcrumbs = (event.breadcrumbs?.isNotEmpty ?? false) - ? event.breadcrumbs - : List.from(_breadcrumbs); - // ignore: deprecated_member_use_from_same_package - event.extra = extra.isNotEmpty ? _mergeEventExtra(event) : event.extra; - } - - if (event is! SentryTransaction) { - event - ..fingerprint = (event.fingerprint?.isNotEmpty ?? false) - ? event.fingerprint - : _fingerprint - ..level = level ?? event.level; - } - - _contexts.forEach((key, value) { - // add the contexts runtime list to the event.contexts.runtimes - if (key == SentryRuntime.listType && - value is List && - value.isNotEmpty) { - _mergeEventContextsRuntimes(value, event); - } else if (key != SentryRuntime.listType && - (!event.contexts.containsKey(key) || event.contexts[key] == null) && - value != null) { - event.contexts[key] = value; - } - }); - - final newSpan = span; - if (event.contexts.trace == null) { - if (newSpan != null) { - event.contexts.trace = newSpan.context.toTraceContext( - sampled: newSpan.samplingDecision?.sampled, - ); - } else { - event.contexts.trace = - SentryTraceContext.fromPropagationContext(propagationContext); - } - } - - return await runEventProcessors(event, hint, _eventProcessors, _options); - } - - /// Merge the scope contexts runtimes and the event contexts runtimes. - void _mergeEventContextsRuntimes( - List values, SentryEvent event) { - for (final runtime in values) { - event.contexts.addRuntime(runtime); - } - } - - /// If the scope and the event have tag entries with the same key, - /// the event tags will be kept. - Map _mergeEventTags(SentryEvent event) => - tags.map((key, value) => MapEntry(key, value))..addAll(event.tags ?? {}); - - /// If the scope and the event have extra entries with the same key, - /// the event extra will be kept. - Map _mergeEventExtra(SentryEvent event) => - extra.map((key, value) => MapEntry(key, value)) - // ignore: deprecated_member_use_from_same_package - ..addAll(event.extra ?? {}); - - /// If scope and event have a user, the user of the event takes - /// precedence. - SentryUser? _mergeUsers(SentryUser? scopeUser, SentryUser? eventUser) { - if (scopeUser == null && eventUser != null) { - return eventUser; - } - if (eventUser == null && scopeUser != null) { - return scopeUser; - } - // otherwise the user of scope takes precedence over the event user - return scopeUser - ?..id = eventUser?.id - ..email = eventUser?.email - ..ipAddress = eventUser?.ipAddress - ..username = eventUser?.username - ..data = _mergeUserData(eventUser?.data, scopeUser.data) - // ignore: deprecated_member_use_from_same_package - ..extras = _mergeUserData(eventUser?.extras, scopeUser.extras); - } - - /// If the User on the scope and the user of an event have extra entries with - /// the same key, the event user extra will be kept. - Map _mergeUserData( - Map? eventData, - Map? scopeData, - ) { - final map = {}; - if (eventData != null) { - map.addAll(eventData); - } - if (scopeData == null) { - return map; - } - for (var value in scopeData.entries) { - map.putIfAbsent(value.key, () => value.value); - } - return map; - } - - /// Clones the current Scope - Scope clone() { - final clone = Scope(_options) - ..level = level - ..fingerprint = List.from(fingerprint) - .._transaction = _transaction - ..span = span - .._enableScopeSync = false - ..propagationContext = propagationContext - .._replayId = _replayId; - - clone._setUserSync(user); - - final tags = List.from(_tags.keys); - for (final tag in tags) { - final value = _tags[tag]; - if (value != null) { - clone._setTagSync(tag, value); - } - } - - for (final extraKey in List.from(_extra.keys)) { - clone._setExtraSync(extraKey, _extra[extraKey]); - } - - for (final breadcrumb in List.from(_breadcrumbs)) { - clone._addBreadCrumbSync(breadcrumb, Hint()); - } - - for (final eventProcessor in List.from(_eventProcessors)) { - clone.addEventProcessor(eventProcessor); - } - - for (final entry in Map.from(contexts).entries) { - if (entry.value != null) { - clone._setContextsSync(entry.key, entry.value); - } - } - - for (final attachment in List.from(_attachments)) { - clone.addAttachment(attachment); - } - - return clone; - } - - Future _callScopeObservers(_OnScopeObserver action) async { - if (_options.enableScopeSync && _enableScopeSync) { - for (final scopeObserver in _options.scopeObservers) { - await action(scopeObserver); - } - } - } -} diff --git a/dart/lib/src/scope_observer.dart b/dart/lib/src/scope_observer.dart deleted file mode 100644 index 09af0c46a0..0000000000 --- a/dart/lib/src/scope_observer.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'dart:async'; - -import 'protocol/breadcrumb.dart'; -import 'protocol/sentry_user.dart'; - -abstract class ScopeObserver { - Future setContexts(String key, dynamic value); - Future removeContexts(String key); - Future setUser(SentryUser? user); - Future addBreadcrumb(Breadcrumb breadcrumb); - Future clearBreadcrumbs(); - Future setExtra(String key, dynamic value); - Future removeExtra(String key); - Future setTag(String key, String value); - Future removeTag(String key); -} diff --git a/dart/lib/src/sdk_lifecycle_hooks.dart b/dart/lib/src/sdk_lifecycle_hooks.dart deleted file mode 100644 index c5ffd6e018..0000000000 --- a/dart/lib/src/sdk_lifecycle_hooks.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'dart:async'; - -import 'package:meta/meta.dart'; - -import '../sentry.dart'; - -@internal -typedef SdkLifecycleCallback = FutureOr - Function(T event); - -@internal -abstract class SdkLifecycleEvent {} - -/// Holds and dispatches SDK lifecycle events in a type-safe way. -/// These are meant to be used internally and are not part of public api. -@internal -class SdkLifecycleRegistry { - SdkLifecycleRegistry(this._options); - - final SentryOptions _options; - final _lifecycleCallbacks = >{}; - - Map> get lifecycleCallbacks => _lifecycleCallbacks; - - void registerCallback( - SdkLifecycleCallback callback) { - _lifecycleCallbacks[T] ??= []; - _lifecycleCallbacks[T]?.add(callback); - } - - void removeCallback( - SdkLifecycleCallback callback) { - final callbacks = _lifecycleCallbacks[T]; - callbacks?.remove(callback); - } - - FutureOr dispatchCallback(T event) async { - final callbacks = _lifecycleCallbacks[event.runtimeType] ?? []; - for (final cb in callbacks) { - try { - final result = (cb as SdkLifecycleCallback)(event); - if (result is Future) { - await result; - } - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'The SDK lifecycle callback threw an exception', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } - } - } -} - -@internal -class OnBeforeCaptureLog extends SdkLifecycleEvent { - OnBeforeCaptureLog(this.log); - - final SentryLog log; -} - -@internal -class OnBeforeSendEvent extends SdkLifecycleEvent { - OnBeforeSendEvent(this.event, this.hint); - - final SentryEvent event; - final Hint hint; -} diff --git a/dart/lib/src/sentry.dart b/dart/lib/src/sentry.dart deleted file mode 100644 index 7b6d79abdf..0000000000 --- a/dart/lib/src/sentry.dart +++ /dev/null @@ -1,438 +0,0 @@ -import 'dart:async'; - -import 'package:meta/meta.dart'; - -import 'dart_exception_type_identifier.dart'; -import 'environment/environment_variables.dart'; -import 'event_processor/deduplication_event_processor.dart'; -import 'event_processor/enricher/enricher_event_processor.dart'; -import 'event_processor/exception/exception_event_processor.dart'; -import 'event_processor/exception/exception_group_event_processor.dart'; -import 'hint.dart'; -import 'hub.dart'; -import 'hub_adapter.dart'; -import 'integration.dart'; -import 'load_dart_debug_images_integration.dart'; -import 'noop_hub.dart'; -import 'noop_isolate_error_integration.dart' - if (dart.library.io) 'isolate_error_integration.dart'; -import 'protocol.dart'; -import 'protocol/sentry_feedback.dart'; -import 'run_zoned_guarded_integration.dart'; -import 'sentry_attachment/sentry_attachment.dart'; -import 'sentry_client.dart'; -import 'sentry_options.dart'; -import 'sentry_run_zoned_guarded.dart'; -import 'tracing.dart'; -import 'transport/data_category.dart'; -import 'transport/task_queue.dart'; -import 'feature_flags_integration.dart'; -import 'sentry_logger.dart'; -import 'logs_enricher_integration.dart'; - -/// Configuration options callback -typedef OptionsConfiguration = FutureOr Function(SentryOptions); - -/// Runs a callback inside of the `runZonedGuarded` method, useful for running your `runApp(MyApp())` -typedef AppRunner = FutureOr Function(); - -/// Sentry SDK main entry point -class Sentry { - static Hub _hub = NoOpHub(); - static TaskQueue _taskQueue = NoOpTaskQueue(); - - Sentry._(); - - /// Initializes the SDK - /// passing a [AppRunner] callback allows to run the app within its own error - /// zone ([`runZonedGuarded`](https://api.dart.dev/stable/2.10.4/dart-async/runZonedGuarded.html)) - static Future init( - OptionsConfiguration optionsConfiguration, { - AppRunner? appRunner, - @internal bool callAppRunnerInRunZonedGuarded = true, - @internal RunZonedGuardedOnError? runZonedGuardedOnError, - @internal SentryOptions? options, - }) async { - final sentryOptions = options ?? SentryOptions(); - - await _initDefaultValues(sentryOptions); - - try { - final config = optionsConfiguration(sentryOptions); - if (config is Future) { - await config; - } - _taskQueue = DefaultTaskQueue( - sentryOptions.maxQueueSize, - sentryOptions.log, - sentryOptions.recorder, - ); - } catch (exception, stackTrace) { - sentryOptions.log( - SentryLevel.error, - 'Error in options configuration.', - exception: exception, - stackTrace: stackTrace, - ); - if (sentryOptions.automatedTestMode) { - rethrow; - } - } - - if (sentryOptions.dsn == null) { - throw ArgumentError('DSN is required.'); - } - - await _init(sentryOptions, appRunner, callAppRunnerInRunZonedGuarded, - runZonedGuardedOnError); - } - - static Future _initDefaultValues(SentryOptions options) async { - _setEnvironmentVariables(options); - - // Throws when running on the browser - if (!options.platform.isWeb) { - // catch any errors that may occur within the entry function, main() - // in the ‘root zone’ where all Dart programs start - options.addIntegrationByIndex(0, IsolateErrorIntegration()); - } - - if (options.runtimeChecker.isDebugMode()) { - options.debug = true; - options.log( - SentryLevel.debug, - 'Debug mode is enabled: Application is running in a debug environment.', - ); - } - - if (options.enableDartSymbolication) { - options.addIntegration(LoadDartDebugImagesIntegration()); - } - - options.addIntegration(FeatureFlagsIntegration()); - options.addIntegration(LogsEnricherIntegration()); - - options.addEventProcessor(EnricherEventProcessor(options)); - options.addEventProcessor(ExceptionEventProcessor(options)); - options.addEventProcessor(DeduplicationEventProcessor(options)); - - options.prependExceptionTypeIdentifier(DartExceptionTypeIdentifier()); - - // Added last to ensure all error events have correct parent/child relationships - options.addEventProcessor(ExceptionGroupEventProcessor(options)); - } - - /// This method reads available environment variables and uses them - /// accordingly. - /// To see which environment variables are available, see [EnvironmentVariables] - /// - /// The precedence of these options are also described on - /// https://docs.sentry.io/platforms/dart/configuration/options/ - static void _setEnvironmentVariables(SentryOptions options) { - final vars = options.environmentVariables; - options.dsn = options.dsn ?? vars.dsn; - - if (options.environment == null) { - var environment = vars.environmentForMode(options.runtimeChecker); - options.environment = vars.environment ?? environment; - } - - options.release = options.release ?? vars.release; - options.dist = options.dist ?? vars.dist; - } - - /// Initializes the SDK - static Future _init( - SentryOptions options, - AppRunner? appRunner, - bool callAppRunnerInRunZonedGuarded, - RunZonedGuardedOnError? runZonedGuardedOnError, - ) async { - if (isEnabled) { - options.log( - SentryLevel.warning, - 'Sentry has been already initialized. Previous configuration will be overwritten.', - ); - } - - // let's set the default values to options - if (await _setDefaultConfiguration(options)) { - final hub = _hub; - _hub = Hub(options); - await hub.close(); - } - - // execute integrations after hub being enabled - if (appRunner != null) { - if (callAppRunnerInRunZonedGuarded) { - var runIntegrationsAndAppRunner = () async { - final integrations = options.integrations - .where((i) => i is! RunZonedGuardedIntegration); - await _callIntegrations(integrations, options); - await appRunner(); - }; - - final runZonedGuardedIntegration = RunZonedGuardedIntegration( - runIntegrationsAndAppRunner, runZonedGuardedOnError); - options.addIntegrationByIndex(0, runZonedGuardedIntegration); - - // RunZonedGuardedIntegration will run other integrations and appRunner - // runZonedGuarded so all exception caught in the error handler are - // handled - await runZonedGuardedIntegration(HubAdapter(), options); - } else { - await _callIntegrations(options.integrations, options); - await appRunner(); - } - } else { - await _callIntegrations(options.integrations, options); - } - } - - static Future _callIntegrations( - Iterable integrations, SentryOptions options) async { - for (final integration in integrations) { - final execute = integration(HubAdapter(), options); - if (execute is Future) { - await execute; - } - } - } - - /// Reports an [event] to Sentry.io. - static Future captureEvent( - SentryEvent event, { - dynamic stackTrace, - Hint? hint, - ScopeCallback? withScope, - }) => - _taskQueue.enqueue( - () => _hub.captureEvent( - event, - stackTrace: stackTrace, - hint: hint, - withScope: withScope, - ), - SentryId.empty(), - event.type != null - ? DataCategory.fromItemType(event.type!) - : DataCategory.unknown); - - /// Reports the [throwable] and optionally its [stackTrace] to Sentry.io. - static Future captureException( - dynamic throwable, { - dynamic stackTrace, - Hint? hint, - SentryMessage? message, - ScopeCallback? withScope, - }) => - _taskQueue.enqueue( - () => _hub.captureException( - throwable, - stackTrace: stackTrace, - hint: hint, - message: message, - withScope: withScope, - ), - SentryId.empty(), - DataCategory.error, - ); - - /// Reports a [message] to Sentry.io. - static Future captureMessage( - String? message, { - SentryLevel? level = SentryLevel.info, - String? template, - List? params, - Hint? hint, - ScopeCallback? withScope, - }) => - _taskQueue.enqueue( - () => _hub.captureMessage( - message, - level: level, - template: template, - params: params, - hint: hint, - withScope: withScope, - ), - SentryId.empty(), - DataCategory.unknown, - ); - - /// Reports [SentryFeedback] to Sentry.io. - /// - /// Use [withScope] to add [SentryAttachment] to the feedback. - static Future captureFeedback( - SentryFeedback feedback, { - Hint? hint, - ScopeCallback? withScope, - }) => - _taskQueue.enqueue( - () => _hub.captureFeedback( - feedback, - hint: hint, - withScope: withScope, - ), - SentryId.empty(), - DataCategory.unknown, - ); - - /// Close the client SDK - static Future close() async { - final hub = _hub; - _hub = NoOpHub(); - await hub.close(); - } - - /// Check if the current Hub is enabled/active. - static bool get isEnabled => _hub.isEnabled; - - /// Last event id recorded by the current Hub - static SentryId get lastEventId => _hub.lastEventId; - - /// Adds a breadcrumb to the current Scope - static Future addBreadcrumb(Breadcrumb crumb, {Hint? hint}) => - _hub.addBreadcrumb(crumb, hint: hint); - - /// Configures the scope through the callback. - static FutureOr configureScope(ScopeCallback callback) => - _hub.configureScope(callback); - - /// Clones the current Hub - static Hub clone() => _hub.clone(); - - /// Binds a different client to the current hub - static void bindClient(SentryClient client) => _hub.bindClient(client); - - static Future _setDefaultConfiguration(SentryOptions options) async { - // if the DSN is empty, let's disable the SDK - if (options.dsn?.isEmpty ?? false) { - await close(); - return false; - } - - // try parsing the dsn - options.parsedDsn; - - return true; - } - - /// Creates a Transaction and returns the instance. - static ISentrySpan startTransaction( - String name, - String operation, { - String? description, - DateTime? startTimestamp, - bool? bindToScope, - bool? waitForChildren, - Duration? autoFinishAfter, - bool? trimEnd, - OnTransactionFinish? onFinish, - Map? customSamplingContext, - }) => - _hub.startTransaction( - name, - operation, - description: description, - startTimestamp: startTimestamp, - bindToScope: bindToScope, - waitForChildren: waitForChildren, - autoFinishAfter: autoFinishAfter, - trimEnd: trimEnd, - onFinish: onFinish, - customSamplingContext: customSamplingContext, - ); - - /// Creates a Transaction and returns the instance. - static ISentrySpan startTransactionWithContext( - SentryTransactionContext transactionContext, { - Map? customSamplingContext, - DateTime? startTimestamp, - bool? bindToScope, - bool? waitForChildren, - Duration? autoFinishAfter, - bool? trimEnd, - OnTransactionFinish? onFinish, - }) => - _hub.startTransactionWithContext( - transactionContext, - customSamplingContext: customSamplingContext, - startTimestamp: startTimestamp, - bindToScope: bindToScope, - waitForChildren: waitForChildren, - autoFinishAfter: autoFinishAfter, - trimEnd: trimEnd, - onFinish: onFinish, - ); - - /// Gets the current active transaction or span bound to the scope. - /// Returns `null` if performance is disabled in the options. - static ISentrySpan? getSpan() => _hub.getSpan(); - - static Future addFeatureFlag(String flag, dynamic result) async { - if (result is! bool) { - return; - } - - final featureFlagsIntegration = currentHub.options.integrations - .whereType() - .firstOrNull; - - if (featureFlagsIntegration == null) { - currentHub.options.log( - SentryLevel.warning, - '$FeatureFlagsIntegration not found. Make sure Sentry is initialized before accessing the addFeatureFlag API.', - ); - return; - } - - await featureFlagsIntegration.addFeatureFlag(flag, result); - } - - @internal - static Hub get currentHub => _hub; - - /// Creates a new error handling zone with Sentry integration using [runZonedGuarded]. - /// - /// This method provides automatic error reporting and breadcrumb tracking while - /// allowing you to define a custom error handling zone. It wraps Dart's native - /// [runZonedGuarded] function with Sentry-specific functionality. - /// - /// This function automatically records calls to `print()` as Breadcrumbs and - /// can be configured using [SentryOptions.enablePrintBreadcrumbs]. - /// - /// ```dart - /// Sentry.runZonedGuarded(() { - /// WidgetsBinding.ensureInitialized(); - /// - /// // Errors before init will not be handled by Sentry - /// - /// SentryFlutter.init( - /// (options) { - /// ... - /// }, - /// appRunner: () => runApp(MyApp()), - /// ); - /// } (error, stackTrace) { - /// // Automatically sends errors to Sentry, no need to do any - /// // captureException calls on your part. - /// // On top of that, you can do your own custom stuff in this callback. - /// }); - /// ``` - static dynamic runZonedGuarded( - R Function() body, - void Function(Object error, StackTrace stack)? onError, { - Map? zoneValues, - ZoneSpecification? zoneSpecification, - }) => - SentryRunZonedGuarded.sentryRunZonedGuarded( - _hub, - body, - onError, - zoneValues: zoneValues, - zoneSpecification: zoneSpecification, - ); - - static SentryLogger get logger => currentHub.options.logger; -} diff --git a/dart/lib/src/sentry_attachment/io_sentry_attachment.dart b/dart/lib/src/sentry_attachment/io_sentry_attachment.dart deleted file mode 100644 index ae07b51508..0000000000 --- a/dart/lib/src/sentry_attachment/io_sentry_attachment.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:io'; - -import 'sentry_attachment.dart'; - -class IoSentryAttachment extends SentryAttachment { - /// Creates an attachment from a given path. - /// Only available on `dart:io` platforms. - /// Not available on web. - IoSentryAttachment.fromPath( - String path, { - String? filename, - String? attachmentType, - String? contentType, - }) : this.fromFile( - File(path), - attachmentType: attachmentType, - contentType: contentType, - filename: filename, - ); - - /// Creates an attachment from a given [File]. - /// Only available on `dart:io` platforms. - /// Not available on web. - IoSentryAttachment.fromFile( - File file, { - String? filename, - super.attachmentType, - super.contentType, - }) : super.fromLoader( - loader: () => file.readAsBytes(), - filename: filename ?? file.uri.pathSegments.last, - ); -} diff --git a/dart/lib/src/sentry_attachment/sentry_attachment.dart b/dart/lib/src/sentry_attachment/sentry_attachment.dart deleted file mode 100644 index 5df3cde578..0000000000 --- a/dart/lib/src/sentry_attachment/sentry_attachment.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; - -import '../protocol/sentry_view_hierarchy.dart'; -import '../utils.dart'; - -// https://develop.sentry.dev/sdk/features/#attachments -// https://develop.sentry.dev/sdk/envelopes/#attachment - -typedef ContentLoader = FutureOr Function(); - -/// Arbitrary content which gets attached to an event. -class SentryAttachment { - /// Standard attachment without special meaning. - static const String typeAttachmentDefault = 'event.attachment'; - - /// Minidump file that creates an error event and is symbolicated. - /// The file should start with the `MDMP` magic bytes. - static const String typeMinidump = 'event.minidump'; - - /// Apple crash report file that creates an error event and is symbolicated. - static const String typeAppleCrashReport = 'event.applecrashreport'; - - /// XML file containing UE4 crash meta data. - /// During event ingestion, event contexts and extra fields are extracted from - /// this file. - static const String typeUnrealContext = 'unreal.context'; - - /// Plain-text log file obtained from UE4 crashes. - /// During event ingestion, the last logs are extracted into event - /// breadcrumbs. - static const String typeUnrealLogs = 'unreal.logs'; - - static const String typeViewHierarchy = 'event.view_hierarchy'; - - SentryAttachment.fromLoader({ - required ContentLoader loader, - required this.filename, - String? attachmentType, - this.contentType, - bool? addToTransactions, - }) : _loader = loader, - attachmentType = attachmentType ?? typeAttachmentDefault, - addToTransactions = addToTransactions ?? false; - - /// Creates an [SentryAttachment] from a [Uint8List] - SentryAttachment.fromUint8List( - Uint8List bytes, - String fileName, { - String? contentType, - String? attachmentType, - bool? addToTransactions, - }) : this.fromLoader( - attachmentType: attachmentType, - loader: () => bytes, - filename: fileName, - contentType: contentType, - addToTransactions: addToTransactions, - ); - - /// Creates an [SentryAttachment] from a [List] - SentryAttachment.fromIntList( - List bytes, - String fileName, { - String? contentType, - String? attachmentType, - bool? addToTransactions, - }) : this.fromLoader( - attachmentType: attachmentType, - loader: () => Uint8List.fromList(bytes), - filename: fileName, - contentType: contentType, - addToTransactions: addToTransactions, - ); - - /// Creates an [SentryAttachment] from [ByteData] - SentryAttachment.fromByteData( - ByteData bytes, - String fileName, { - String? contentType, - String? attachmentType, - bool? addToTransactions, - }) : this.fromLoader( - attachmentType: attachmentType, - loader: () => bytes.buffer.asUint8List(), - filename: fileName, - contentType: contentType, - addToTransactions: addToTransactions, - ); - - SentryAttachment.fromScreenshotData(Uint8List bytes) - : this.fromUint8List( - bytes, - 'screenshot.png', - contentType: 'image/png', - attachmentType: SentryAttachment.typeAttachmentDefault, - ); - - SentryAttachment.fromViewHierarchy(SentryViewHierarchy sentryViewHierarchy) - : this.fromLoader( - loader: () => Uint8List.fromList( - utf8JsonEncoder.convert(sentryViewHierarchy.toJson())), - filename: 'view-hierarchy.json', - contentType: 'application/json', - attachmentType: SentryAttachment.typeViewHierarchy, - ); - - /// Attachment type. - /// Should be one of the static types declared in [SentryAttachment]. - final String attachmentType; - - /// Attachment content. - /// Is loaded while sending this attachment. - FutureOr get bytes => _loader(); - - final ContentLoader _loader; - - /// Attachment file name. - final String filename; - - /// Attachment content type. - /// Inferred by Sentry if it's not given. - final String? contentType; - - /// If true, attachment should be added to every transaction. - /// Defaults to false. - final bool addToTransactions; -} diff --git a/dart/lib/src/sentry_baggage.dart b/dart/lib/src/sentry_baggage.dart deleted file mode 100644 index 7baecc5309..0000000000 --- a/dart/lib/src/sentry_baggage.dart +++ /dev/null @@ -1,222 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'protocol.dart'; -import 'scope.dart'; -import 'sentry_options.dart'; - -class SentryBaggage { - static const String _sampleRateKeyName = 'sentry-sample_rate'; - static const String _sampleRandKeyName = 'sentry-sample_rand'; - - static const int _maxChars = 8192; - static const int _maxListMember = 64; - - SentryBaggage( - this._keyValues, { - this.log, - }); - - final Map _keyValues; - final SdkLogCallback? log; - - String toHeaderString() { - final buffer = StringBuffer(); - var listMemberCount = 0; - var separator = ''; - - for (final entry in _keyValues.entries) { - if (listMemberCount >= _maxListMember) { - log?.call( - SentryLevel.info, - 'Baggage key ${entry.key} dropped because of max list member.', - ); - break; - } - try { - final encodedKey = _urlEncode(entry.key); - final encodedValue = _urlEncode(entry.value); - final encodedKeyValue = '$separator$encodedKey=$encodedValue'; - - final totalLengthIfValueAdded = buffer.length + encodedKeyValue.length; - - if (totalLengthIfValueAdded >= _maxChars) { - log?.call( - SentryLevel.info, - 'Baggage key ${entry.key} dropped because of max baggage chars.', - ); - continue; - } - - listMemberCount++; - buffer.write(encodedKeyValue); - separator = ','; - } catch (exception, stackTrace) { - log?.call( - SentryLevel.error, - 'Failed to parse the baggage key ${entry.key}.', - exception: exception, - stackTrace: stackTrace, - ); - // TODO rethrow in options.automatedTestMode (currently not available here to check) - } - } - - return buffer.toString(); - } - - factory SentryBaggage.fromHeaderList( - List headerValues, { - SdkLogCallback? log, - }) { - final keyValues = {}; - - for (final headerValue in headerValues) { - final keyValuesToAdd = _extractKeyValuesFromBaggageString( - headerValue, - log: log, - ); - keyValues.addAll(keyValuesToAdd); - } - - return SentryBaggage(keyValues, log: log); - } - - factory SentryBaggage.fromHeader( - String headerValue, { - SdkLogCallback? log, - }) { - final keyValues = _extractKeyValuesFromBaggageString( - headerValue, - log: log, - ); - - return SentryBaggage(keyValues, log: log); - } - - @internal - void setValuesFromScope(Scope scope, SentryOptions options) { - final propagationContext = scope.propagationContext; - setTraceId(propagationContext.traceId.toString()); - setPublicKey(options.parsedDsn.publicKey); - if (options.release != null) { - setRelease(options.release!); - } - if (options.environment != null) { - setEnvironment(options.environment!); - } - if (scope.user?.id != null) { - setUserId(scope.user!.id!); - } - if (scope.replayId != null && scope.replayId != SentryId.empty()) { - setReplayId(scope.replayId.toString()); - } - } - - static Map _extractKeyValuesFromBaggageString( - String headerValue, { - SdkLogCallback? log, - }) { - final keyValues = {}; - - final keyValueStrings = headerValue.split(','); - - for (final keyValueString in keyValueStrings) { - // TODO: Note, value MAY contain any number of the equal sign (=) characters. - // Parsers MUST NOT assume that the equal sign is only used to separate key and value. - final keyAndValue = keyValueString.split('='); - if (keyAndValue.length == 2) { - try { - final key = _urlDecode(keyAndValue.first.trim()); - final value = _urlDecode(keyAndValue.last.trim()); - keyValues[key] = value; - } catch (exception, stackTrace) { - log?.call( - SentryLevel.error, - 'Failed to parse the baggage entry $keyAndValue.', - exception: exception, - stackTrace: stackTrace, - ); - } - } - } - - return keyValues; - } - - static String _urlDecode(String uri) { - return Uri.decodeComponent(uri); - } - - String _urlEncode(String uri) { - return Uri.encodeComponent(uri); - } - - String? get(String key) => _keyValues[key]; - - void set(String key, String value) { - _keyValues[key] = value; - } - - void setTraceId(String value) { - set('sentry-trace_id', value); - } - - void setPublicKey(String value) { - set('sentry-public_key', value); - } - - void setEnvironment(String value) { - set('sentry-environment', value); - } - - void setRelease(String value) { - set('sentry-release', value); - } - - void setUserId(String value) { - set('sentry-user_id', value); - } - - void setTransaction(String value) { - set('sentry-transaction', value); - } - - void setSampleRate(String value) { - set(_sampleRateKeyName, value); - } - - void setSampleRand(String value) { - set(_sampleRandKeyName, value); - } - - void setSampled(String value) { - set('sentry-sampled', value); - } - - double? getSampleRate() { - final sampleRate = get(_sampleRateKeyName); - if (sampleRate == null) { - return null; - } - - return double.tryParse(sampleRate); - } - - double? getSampleRand() { - final sampleRand = get(_sampleRandKeyName); - if (sampleRand == null) { - return null; - } - - return double.tryParse(sampleRand); - } - - void setReplayId(String value) => set('sentry-replay_id', value); - - SentryId? getReplayId() { - final replayId = get('sentry-replay_id'); - return replayId == null ? null : SentryId.fromId(replayId); - } - - Map get keyValues => Map.unmodifiable(_keyValues); -} diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart deleted file mode 100644 index 7b27aa63e3..0000000000 --- a/dart/lib/src/sentry_client.dart +++ /dev/null @@ -1,688 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:meta/meta.dart'; - -import 'client_reports/client_report_recorder.dart'; -import 'client_reports/discard_reason.dart'; -import 'event_processor/run_event_processors.dart'; -import 'hint.dart'; -import 'sdk_lifecycle_hooks.dart'; -import 'protocol.dart'; -import 'protocol/sentry_feedback.dart'; -import 'scope.dart'; -import 'sentry_attachment/sentry_attachment.dart'; -import 'sentry_baggage.dart'; -import 'sentry_envelope.dart'; -import 'sentry_exception_factory.dart'; -import 'sentry_options.dart'; -import 'sentry_stack_trace_factory.dart'; -import 'sentry_trace_context_header.dart'; -import 'transport/client_report_transport.dart'; -import 'transport/data_category.dart'; -import 'transport/http_transport.dart'; -import 'transport/noop_transport.dart'; -import 'transport/rate_limiter.dart'; -import 'transport/spotlight_http_transport.dart'; -import 'type_check_hint.dart'; -import 'utils/isolate_utils.dart'; -import 'utils/regex_utils.dart'; -import 'utils/stacktrace_utils.dart'; -import 'sentry_log_batcher.dart'; -import 'version.dart'; - -/// Default value for [SentryUser.ipAddress]. It gets set when an event does not have -/// a user and IP address. Only applies if [SentryOptions.sendDefaultPii] is set -/// to true. -const _defaultIpAddress = '{{auto}}'; - -@visibleForTesting -String get defaultIpAddress => _defaultIpAddress; - -/// Logs crash reports and events to the Sentry.io service. -class SentryClient { - final SentryOptions _options; - final Random? _random; - - static final _emptySentryId = Future.value(SentryId.empty()); - - late final SdkLifecycleRegistry _lifecycleRegistry; - - /// Allows registration and dispatching of callbacks outside of SentryClient - @internal - SdkLifecycleRegistry get lifeCycleRegistry => _lifecycleRegistry; - - SentryExceptionFactory get _exceptionFactory => _options.exceptionFactory; - SentryStackTraceFactory get _stackTraceFactory => _options.stackTraceFactory; - - /// Instantiates a client using [SentryOptions] - factory SentryClient(SentryOptions options) { - if (options.sendClientReports) { - options.recorder = ClientReportRecorder(options.clock); - } - RateLimiter? rateLimiter; - if (options.transport is NoOpTransport) { - rateLimiter = RateLimiter(options); - options.transport = HttpTransport(options, rateLimiter); - } - // rateLimiter is null if FileSystemTransport is active since Native SDKs take care of rate limiting - options.transport = ClientReportTransport( - rateLimiter, - options, - options.transport, - ); - // TODO: Use spotlight integration directly through JS SDK, then we can remove isWeb check - final enableFlutterSpotlight = (options.spotlight.enabled && - (options.platform.isWeb || - options.platform.isLinux || - options.platform.isWindows)); - // Spotlight in the Flutter layer is only enabled for Web, Linux and Windows - // Other platforms use spotlight through their native SDKs - if (enableFlutterSpotlight) { - options.transport = SpotlightHttpTransport(options, options.transport); - } - if (options.enableLogs) { - options.logBatcher = SentryLogBatcher(options); - } - return SentryClient._(options); - } - - /// Instantiates a client using [SentryOptions] - SentryClient._(this._options) - : _random = _options.sampleRate == null ? null : Random() { - _lifecycleRegistry = SdkLifecycleRegistry(_options); - } - - /// Reports an [event] to Sentry.io. - Future captureEvent( - SentryEvent event, { - Scope? scope, - dynamic stackTrace, - Hint? hint, - }) async { - if (_isIgnoredError(event)) { - _options.log( - SentryLevel.debug, - 'Error was ignored as specified in the ignoredErrors options.', - ); - _options.recorder - .recordLostEvent(DiscardReason.ignored, _getCategory(event)); - return _emptySentryId; - } - - if (_options.containsIgnoredExceptionForType(event.throwable)) { - _options.log( - SentryLevel.debug, - 'Event was dropped as the exception ${event.throwable.runtimeType.toString()} is ignored.', - ); - _options.recorder - .recordLostEvent(DiscardReason.eventProcessor, _getCategory(event)); - return _emptySentryId; - } - - if (_sampleRate() && event.type != 'feedback') { - _options.recorder - .recordLostEvent(DiscardReason.sampleRate, _getCategory(event)); - _options.log( - SentryLevel.debug, - 'Event ${event.eventId.toString()} was dropped due to sampling decision.', - ); - return _emptySentryId; - } - - hint ??= Hint(); - - SentryEvent? preparedEvent = - _prepareEvent(event, hint, stackTrace: stackTrace); - - if (scope != null) { - preparedEvent = await scope.applyToEvent(preparedEvent, hint); - } else { - _options.log( - SentryLevel.debug, 'No scope to apply on event was provided'); - } - - // dropped by scope event processors - if (preparedEvent == null) { - return _emptySentryId; - } - - preparedEvent = await runEventProcessors( - preparedEvent, - hint, - _options.eventProcessors, - _options, - ); - - // dropped by event processors - if (preparedEvent == null) { - return _emptySentryId; - } - - preparedEvent = _createUserOrSetDefaultIpAddress(preparedEvent); - - preparedEvent = await _runBeforeSend( - preparedEvent, - hint, - ); - - // dropped by beforeSend - if (preparedEvent == null) { - return _emptySentryId; - } - - // Event is fully processed and ready to be sent - await _lifecycleRegistry - .dispatchCallback(OnBeforeSendEvent(preparedEvent, hint)); - - var attachments = List.from(scope?.attachments ?? []); - attachments.addAll(hint.attachments); - var screenshot = hint.screenshot; - if (screenshot != null) { - attachments.add(screenshot); - } - - var viewHierarchy = hint.viewHierarchy; - if (viewHierarchy != null && event.type != 'feedback') { - attachments.add(viewHierarchy); - } - - var traceContext = scope?.span?.traceContext(); - if (traceContext == null) { - if (scope != null) { - scope.propagationContext.baggage ??= - SentryBaggage({}, log: _options.log) - ..setValuesFromScope(scope, _options); - traceContext = SentryTraceContextHeader.fromBaggage( - scope.propagationContext.baggage!); - } - } else { - traceContext.replayId = scope?.replayId; - } - - final envelope = SentryEnvelope.fromEvent( - preparedEvent, - _options.sdk, - dsn: _options.dsn, - traceContext: traceContext, - attachments: attachments.isNotEmpty ? attachments : null, - ); - - final id = await captureEnvelope(envelope); - return id ?? SentryId.empty(); - } - - bool _isIgnoredError(SentryEvent event) { - if (event.message == null || _options.ignoreErrors.isEmpty) { - return false; - } - - var message = event.message!.formatted; - return isMatchingRegexPattern(message, _options.ignoreErrors); - } - - SentryEvent _prepareEvent(SentryEvent event, Hint hint, - {dynamic stackTrace}) { - event - ..serverName = event.serverName ?? _options.serverName - ..dist = event.dist ?? _options.dist - ..environment = event.environment ?? _options.environment - ..release = event.release ?? _options.release - ..sdk = event.sdk ?? _options.sdk - ..platform = event.platform ?? sdkPlatform(_options.platform.isWeb); - - if (event is SentryTransaction) { - return event; - } - - if (event.type == 'feedback') { - return event; - } - - if (event.exceptions?.isNotEmpty ?? false) { - return event; - } - - final isolateName = getIsolateName(); - // Isolates have no id, so the hashCode of the name will be used as id - final isolateId = isolateName?.hashCode; - - if (event.throwableMechanism != null) { - final extractedExceptionCauses = _exceptionFactory.extractor - .flatten(event.throwableMechanism, stackTrace); - - SentryException? rootException; - SentryException? currentException; - final sentryThreads = []; - - for (final extractedExceptionCause in extractedExceptionCauses) { - var sentryException = _exceptionFactory.getSentryException( - extractedExceptionCause.exception, - stackTrace: extractedExceptionCause.stackTrace, - removeSentryFrames: hint.get(TypeCheckHint.currentStackTrace), - ); - if (extractedExceptionCause.source != null) { - var mechanism = - sentryException.mechanism ?? Mechanism(type: "generic"); - - mechanism.source = extractedExceptionCause.source; - sentryException.mechanism = mechanism; - } - - SentryThread? sentryThread; - - if (!_options.platform.isWeb && - isolateName != null && - _options.attachThreads) { - sentryException.threadId = isolateId; - sentryThread = SentryThread( - id: isolateId, - name: isolateName, - crashed: true, - current: true, - ); - } - - rootException ??= sentryException; - currentException?.addException(sentryException); - currentException = sentryException; - - if (sentryThread != null) { - sentryThreads.add(sentryThread); - } - } - - final exceptions = [...?event.exceptions]; - if (rootException != null) { - exceptions.add(rootException); - } - return event - ..exceptions = exceptions - ..threads = [ - ...?event.threads, - ...sentryThreads, - ]; - } - - // The stacktrace is not part of an exception, - // therefore add it to the threads. - // https://develop.sentry.dev/sdk/event-payloads/stacktrace/ - if (stackTrace != null || _options.attachStacktrace) { - if (stackTrace == null || stackTrace == StackTrace.empty) { - stackTrace = getCurrentStackTrace(); - hint.addAll({TypeCheckHint.currentStackTrace: true}); - } - final sentryStackTrace = _stackTraceFactory.parse( - stackTrace, - removeSentryFrames: hint.get(TypeCheckHint.currentStackTrace), - ); - if (sentryStackTrace.frames.isNotEmpty) { - event.threads = [ - ...?event.threads, - SentryThread( - name: isolateName, - id: isolateId, - crashed: false, - current: true, - stacktrace: sentryStackTrace, - ), - ]; - } - } - - return event; - } - - SentryEvent _createUserOrSetDefaultIpAddress(SentryEvent event) { - var user = event.user; - final effectiveIpAddress = - user?.ipAddress ?? (_options.sendDefaultPii ? _defaultIpAddress : null); - - if (effectiveIpAddress != null) { - user ??= SentryUser(ipAddress: effectiveIpAddress); - user.ipAddress = effectiveIpAddress; - event.user = user; - } - - return event; - } - - /// Reports the [throwable] and optionally its [stackTrace] to Sentry.io. - Future captureException( - dynamic throwable, { - dynamic stackTrace, - Scope? scope, - Hint? hint, - }) { - final event = SentryEvent( - throwable: throwable, - timestamp: _options.clock(), - ); - - return captureEvent( - event, - stackTrace: stackTrace, - scope: scope, - hint: hint, - ); - } - - /// Reports the [template] - Future captureMessage( - String formatted, { - SentryLevel? level, - String? template, - List? params, - Scope? scope, - Hint? hint, - }) { - final event = SentryEvent( - message: SentryMessage(formatted, template: template, params: params), - level: level ?? SentryLevel.info, - timestamp: _options.clock(), - ); - - return captureEvent(event, scope: scope, hint: hint); - } - - @internal - Future captureTransaction( - SentryTransaction transaction, { - Scope? scope, - SentryTraceContextHeader? traceContext, - Hint? hint, - }) async { - hint ??= Hint(); - - SentryTransaction? preparedTransaction = - _prepareEvent(transaction, hint) as SentryTransaction; - - if (scope != null) { - preparedTransaction = await scope.applyToEvent(preparedTransaction, hint) - as SentryTransaction?; - } else { - _options.log( - SentryLevel.debug, 'No scope to apply on transaction was provided'); - } - - // dropped by scope event processors - if (preparedTransaction == null) { - return _emptySentryId; - } - - preparedTransaction = await runEventProcessors( - preparedTransaction, - hint, - _options.eventProcessors, - _options, - ) as SentryTransaction?; - - // dropped by event processors - if (preparedTransaction == null) { - return _emptySentryId; - } - - if (_isIgnoredTransaction(preparedTransaction)) { - _options.log( - SentryLevel.debug, - 'Transaction was ignored as specified in the ignoredTransactions options.', - ); - - _options.recorder.recordLostEvent( - DiscardReason.ignored, _getCategory(preparedTransaction)); - return _emptySentryId; - } - - preparedTransaction = _createUserOrSetDefaultIpAddress(preparedTransaction) - as SentryTransaction; - - preparedTransaction = - await _runBeforeSend(preparedTransaction, hint) as SentryTransaction?; - - // dropped by beforeSendTransaction - if (preparedTransaction == null) { - return _emptySentryId; - } - - final attachments = scope?.attachments - .where((element) => element.addToTransactions) - .toList(); - final envelope = SentryEnvelope.fromTransaction( - preparedTransaction, - _options.sdk, - dsn: _options.dsn, - traceContext: traceContext, - attachments: attachments, - ); - - final profileInfo = preparedTransaction.tracer.profileInfo; - if (profileInfo != null) { - envelope.items.add(profileInfo.asEnvelopeItem()); - } - final id = await captureEnvelope(envelope); - - return id ?? SentryId.empty(); - } - - bool _isIgnoredTransaction(SentryTransaction transaction) { - if (_options.ignoreTransactions.isEmpty) { - return false; - } - - var name = transaction.tracer.name; - return isMatchingRegexPattern(name, _options.ignoreTransactions); - } - - /// Reports the [envelope] to Sentry.io. - Future captureEnvelope(SentryEnvelope envelope) { - return _options.transport.send(envelope); - } - - /// Reports the [feedback] to Sentry.io. - Future captureFeedback( - SentryFeedback feedback, { - Scope? scope, - Hint? hint, - }) { - // Cap feedback messages to max 4096 characters - if (feedback.message.length > 4096) { - feedback.message = feedback.message.substring(0, 4096); - } - final feedbackEvent = SentryEvent( - type: 'feedback', - contexts: Contexts(feedback: feedback), - level: SentryLevel.info, - ); - - return captureEvent( - feedbackEvent, - scope: scope, - hint: hint, - ); - } - - @internal - FutureOr captureLog( - SentryLog log, { - Scope? scope, - }) async { - if (!_options.enableLogs) { - return; - } - - log.attributes['sentry.sdk.name'] = SentryLogAttribute.string( - _options.sdk.name, - ); - log.attributes['sentry.sdk.version'] = SentryLogAttribute.string( - _options.sdk.version, - ); - final environment = _options.environment; - if (environment != null) { - log.attributes['sentry.environment'] = SentryLogAttribute.string( - environment, - ); - } - final release = _options.release; - if (release != null) { - log.attributes['sentry.release'] = SentryLogAttribute.string( - release, - ); - } - - final propagationContext = scope?.propagationContext; - if (propagationContext != null) { - log.traceId = propagationContext.traceId; - } - final span = scope?.span; - if (span != null) { - log.attributes['sentry.trace.parent_span_id'] = SentryLogAttribute.string( - span.context.spanId.toString(), - ); - } - - final user = scope?.user; - final id = user?.id; - final email = user?.email; - final name = user?.name; - if (id != null) { - log.attributes['user.id'] = SentryLogAttribute.string(id); - } - if (name != null) { - log.attributes['user.name'] = SentryLogAttribute.string(name); - } - if (email != null) { - log.attributes['user.email'] = SentryLogAttribute.string(email); - } - - final beforeSendLog = _options.beforeSendLog; - SentryLog? processedLog = log; - if (beforeSendLog != null) { - try { - final callbackResult = beforeSendLog(log); - - if (callbackResult is Future) { - processedLog = await callbackResult; - } else { - processedLog = callbackResult; - } - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'The beforeSendLog callback threw an exception', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } - } - - if (processedLog != null) { - await _lifecycleRegistry - .dispatchCallback(OnBeforeCaptureLog(processedLog)); - _options.logBatcher.addLog(processedLog); - } else { - _options.recorder.recordLostEvent( - DiscardReason.beforeSend, - DataCategory.logItem, - ); - } - } - - void close() { - _options.httpClient.close(); - } - - Future _runBeforeSend( - SentryEvent event, - Hint hint, - ) async { - SentryEvent? processedEvent = event; - final spanCountBeforeCallback = - event is SentryTransaction ? event.spans.length : 0; - - final beforeSend = _options.beforeSend; - final beforeSendTransaction = _options.beforeSendTransaction; - final beforeSendFeedback = _options.beforeSendFeedback; - String beforeSendName = 'beforeSend'; - - try { - if (event is SentryTransaction && beforeSendTransaction != null) { - beforeSendName = 'beforeSendTransaction'; - final callbackResult = beforeSendTransaction(event, hint); - if (callbackResult is Future) { - processedEvent = await callbackResult; - } else { - processedEvent = callbackResult; - } - } else if (event.type == 'feedback' && beforeSendFeedback != null) { - final callbackResult = beforeSendFeedback(event, hint); - if (callbackResult is Future) { - processedEvent = await callbackResult; - } else { - processedEvent = callbackResult; - } - } else if (beforeSend != null) { - final callbackResult = beforeSend(event, hint); - if (callbackResult is Future) { - processedEvent = await callbackResult; - } else { - processedEvent = callbackResult; - } - } - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'The $beforeSendName callback threw an exception', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } - - final discardReason = DiscardReason.beforeSend; - if (processedEvent == null) { - _options.recorder.recordLostEvent(discardReason, _getCategory(event)); - if (event is SentryTransaction) { - // We dropped the whole transaction, the dropped count includes all child spans + 1 root span - _options.recorder.recordLostEvent(discardReason, DataCategory.span, - count: spanCountBeforeCallback + 1); - } - _options.log( - SentryLevel.debug, - '${event.runtimeType} was dropped by $beforeSendName callback', - ); - } else if (event is SentryTransaction && - processedEvent is SentryTransaction) { - // If beforeSend removed only some spans we still report them as dropped - final spanCountAfterCallback = processedEvent.spans.length; - final droppedSpanCount = spanCountBeforeCallback - spanCountAfterCallback; - if (droppedSpanCount > 0) { - _options.recorder.recordLostEvent(discardReason, DataCategory.span, - count: droppedSpanCount); - } - } - - return processedEvent; - } - - bool _sampleRate() { - if (_options.sampleRate != null && _random != null) { - return (_options.sampleRate! < _random.nextDouble()); - } - return false; - } - - DataCategory _getCategory(SentryEvent event) { - if (event is SentryTransaction) { - return DataCategory.transaction; - } else if (event.type == 'feedback') { - return DataCategory.feedback; - } else { - return DataCategory.error; - } - } -} diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart deleted file mode 100644 index 83cccb14b7..0000000000 --- a/dart/lib/src/sentry_envelope.dart +++ /dev/null @@ -1,137 +0,0 @@ -import 'dart:convert'; - -import 'client_reports/client_report.dart'; -import 'protocol.dart'; -import 'sentry_attachment/sentry_attachment.dart'; -import 'sentry_envelope_header.dart'; -import 'sentry_envelope_item.dart'; -import 'sentry_item_type.dart'; -import 'sentry_options.dart'; -import 'sentry_trace_context_header.dart'; -import 'utils.dart'; - -/// Class representation of `Envelope` file. -class SentryEnvelope { - SentryEnvelope(this.header, this.items, - {this.containsUnhandledException = false}); - - /// Header describing envelope content. - final SentryEnvelopeHeader header; - - /// All items contained in the envelope. - final List items; - - /// Whether the envelope contains an unhandled exception. - /// This is used to determine if the native SDK should start a new session. - final bool containsUnhandledException; - - /// Create a [SentryEnvelope] containing one [SentryEnvelopeItem] which holds the [SentryEvent] data. - factory SentryEnvelope.fromEvent( - SentryEvent event, - SdkVersion sdkVersion, { - String? dsn, - SentryTraceContextHeader? traceContext, - List? attachments, - }) { - bool containsUnhandledException = false; - - if (event.exceptions != null && event.exceptions!.isNotEmpty) { - // Check all exceptions for any unhandled ones - containsUnhandledException = event.exceptions!.any((exception) { - return exception.mechanism?.handled == false; - }); - } - - return SentryEnvelope( - SentryEnvelopeHeader( - event.eventId, - sdkVersion, - dsn: dsn, - traceContext: traceContext, - ), - [ - SentryEnvelopeItem.fromEvent(event), - if (attachments != null) - ...attachments.map((e) => SentryEnvelopeItem.fromAttachment(e)) - ], - containsUnhandledException: containsUnhandledException, - ); - } - - /// Create a [SentryEnvelope] containing one [SentryEnvelopeItem] which holds the [SentryTransaction] data. - factory SentryEnvelope.fromTransaction( - SentryTransaction transaction, - SdkVersion sdkVersion, { - String? dsn, - SentryTraceContextHeader? traceContext, - List? attachments, - }) { - return SentryEnvelope( - SentryEnvelopeHeader( - transaction.eventId, - sdkVersion, - dsn: dsn, - traceContext: traceContext, - ), - [ - SentryEnvelopeItem.fromTransaction(transaction), - if (attachments != null) - ...attachments.map((e) => SentryEnvelopeItem.fromAttachment(e)) - ], - ); - } - - factory SentryEnvelope.fromLogs( - List items, - SdkVersion sdkVersion, - ) { - return SentryEnvelope( - SentryEnvelopeHeader( - null, - sdkVersion, - ), - [ - SentryEnvelopeItem.fromLogs(items), - ], - ); - } - - /// Stream binary data representation of `Envelope` file encoded. - Stream> envelopeStream(SentryOptions options) async* { - yield utf8JsonEncoder.convert(header.toJson()); - - final newLineData = utf8.encode('\n'); - for (final item in items) { - try { - final dataFuture = item.dataFactory(); - final data = dataFuture is Future ? await dataFuture : dataFuture; - - // Only attachments should be filtered according to - // SentryOptions.maxAttachmentSize - if (item.header.type == SentryItemType.attachment && - data.length > options.maxAttachmentSize) { - continue; - } - - yield newLineData; - yield utf8JsonEncoder.convert(await item.header.toJson(data.length)); - yield newLineData; - yield data; - } catch (_) { - if (options.automatedTestMode) { - rethrow; - } - // Skip throwing envelope item data closure. - continue; - } - } - } - - /// Add an envelope item containing client report data. - void addClientReport(ClientReport? clientReport) { - if (clientReport != null) { - final envelopeItem = SentryEnvelopeItem.fromClientReport(clientReport); - items.add(envelopeItem); - } - } -} diff --git a/dart/lib/src/sentry_envelope_header.dart b/dart/lib/src/sentry_envelope_header.dart deleted file mode 100644 index b7398985cc..0000000000 --- a/dart/lib/src/sentry_envelope_header.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'protocol/sentry_id.dart'; -import 'protocol/sdk_version.dart'; -import 'sentry_trace_context_header.dart'; -import 'utils.dart'; - -/// Header containing `SentryId` and `SdkVersion`. -class SentryEnvelopeHeader { - SentryEnvelopeHeader( - this.eventId, - this.sdkVersion, { - this.dsn, - this.traceContext, - this.sentAt, - }); - SentryEnvelopeHeader.newEventId() - : eventId = SentryId.newId(), - sdkVersion = null, - dsn = null, - traceContext = null, - sentAt = null; - - /// The identifier of encoded `SentryEvent`. - final SentryId? eventId; - - /// The `SdkVersion` with which the envelope was send. - final SdkVersion? sdkVersion; - - final SentryTraceContextHeader? traceContext; - - /// The `DSN` of the Sentry project. - final String? dsn; - - DateTime? sentAt; - - /// Header encoded as JSON - Map toJson() { - final json = {}; - final tempEventId = eventId; - - if (tempEventId != null) { - json['event_id'] = tempEventId.toString(); - } - - final tempSdkVersion = sdkVersion; - if (tempSdkVersion != null) { - json['sdk'] = tempSdkVersion.toJson(); - } - - final tempTraceContext = traceContext; - if (tempTraceContext != null) { - json['trace'] = tempTraceContext.toJson(); - } - - if (dsn != null) { - json['dsn'] = dsn; - } - - if (sentAt != null) { - json['sent_at'] = formatDateAsIso8601WithMillisPrecision(sentAt!); - } - - return json; - } -} diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart deleted file mode 100644 index 06261380db..0000000000 --- a/dart/lib/src/sentry_envelope_item.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'dart:async'; - -import 'client_reports/client_report.dart'; -import 'protocol.dart'; -import 'sentry_attachment/sentry_attachment.dart'; -import 'sentry_envelope_item_header.dart'; -import 'sentry_item_type.dart'; -import 'utils.dart'; - -/// Item holding header information and JSON encoded data. -class SentryEnvelopeItem { - /// The original, non-encoded object, used when direct access to the source data is needed. - Object? originalObject; - - SentryEnvelopeItem(this.header, this.dataFactory, {this.originalObject}); - - /// Creates a [SentryEnvelopeItem] which sends [SentryTransaction]. - factory SentryEnvelopeItem.fromTransaction(SentryTransaction transaction) { - final header = SentryEnvelopeItemHeader( - SentryItemType.transaction, - contentType: 'application/json', - ); - return SentryEnvelopeItem( - header, () => utf8JsonEncoder.convert(transaction.toJson()), - originalObject: transaction); - } - - factory SentryEnvelopeItem.fromAttachment(SentryAttachment attachment) { - final header = SentryEnvelopeItemHeader( - SentryItemType.attachment, - contentType: attachment.contentType, - fileName: attachment.filename, - attachmentType: attachment.attachmentType, - ); - return SentryEnvelopeItem( - header, - () => attachment.bytes, - originalObject: attachment, - ); - } - - /// Create a [SentryEnvelopeItem] which holds the [SentryEvent] data. - factory SentryEnvelopeItem.fromEvent(SentryEvent event) { - return SentryEnvelopeItem( - SentryEnvelopeItemHeader( - event.type == 'feedback' ? 'feedback' : SentryItemType.event, - contentType: 'application/json', - ), - () => utf8JsonEncoder.convert(event.toJson()), - originalObject: event, - ); - } - - /// Create a [SentryEnvelopeItem] which holds the [ClientReport] data. - factory SentryEnvelopeItem.fromClientReport(ClientReport clientReport) { - return SentryEnvelopeItem( - SentryEnvelopeItemHeader( - SentryItemType.clientReport, - contentType: 'application/json', - ), - () => utf8JsonEncoder.convert(clientReport.toJson()), - originalObject: clientReport, - ); - } - - factory SentryEnvelopeItem.fromLogs(List items) { - final payload = { - 'items': items.map((e) => e.toJson()).toList(), - }; - return SentryEnvelopeItem( - SentryEnvelopeItemHeader( - SentryItemType.log, - itemCount: items.length, - contentType: 'application/vnd.sentry.items.log+json', - ), - () => utf8JsonEncoder.convert(payload), - originalObject: payload, - ); - } - - /// Header with info about type and length of data in bytes. - final SentryEnvelopeItemHeader header; - - /// Create binary data representation of item data. - final FutureOr> Function() dataFactory; -} diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart deleted file mode 100644 index 31ff3d8602..0000000000 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ /dev/null @@ -1,33 +0,0 @@ -/// Header with item info about type and length of data in bytes. -class SentryEnvelopeItemHeader { - SentryEnvelopeItemHeader( - this.type, { - this.itemCount, - this.contentType, - this.fileName, - this.attachmentType, - }); - - /// Type of encoded data. - final String type; - - final int? itemCount; - - final String? contentType; - - final String? fileName; - - final String? attachmentType; - - /// Item header encoded as JSON - Future> toJson(int length) async { - return { - if (itemCount != null) 'item_count': itemCount, - if (contentType != null) 'content_type': contentType, - if (fileName != null) 'filename': fileName, - if (attachmentType != null) 'attachment_type': attachmentType, - 'type': type, - 'length': length, - }; - } -} diff --git a/dart/lib/src/sentry_event_like.dart b/dart/lib/src/sentry_event_like.dart deleted file mode 100644 index 4117e24261..0000000000 --- a/dart/lib/src/sentry_event_like.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'protocol.dart'; - -/// A [SentryEventLike] mixin that is extended by [SentryEvent] and [SentryTransaction] -mixin SentryEventLike { - @Deprecated('Assign values directly to the instance.') - T copyWith(); -} diff --git a/dart/lib/src/sentry_exception_factory.dart b/dart/lib/src/sentry_exception_factory.dart deleted file mode 100644 index 15d474bf4f..0000000000 --- a/dart/lib/src/sentry_exception_factory.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'protocol.dart'; -import 'recursive_exception_cause_extractor.dart'; -import 'sentry_options.dart'; -import 'sentry_stack_trace_factory.dart'; -import 'throwable_mechanism.dart'; -import 'utils/stacktrace_utils.dart'; - -/// class to convert Dart Error and exception to SentryException -class SentryExceptionFactory { - final SentryOptions _options; - - SentryStackTraceFactory get _stacktraceFactory => _options.stackTraceFactory; - - late final extractor = RecursiveExceptionCauseExtractor(_options); - - SentryExceptionFactory(this._options); - - SentryException getSentryException( - dynamic exception, { - dynamic stackTrace, - bool? removeSentryFrames, - }) { - var throwable = exception; - Mechanism? mechanism; - bool? snapshot; - if (exception is ThrowableMechanism) { - throwable = exception.throwable; - mechanism = exception.mechanism; - snapshot = exception.snapshot; - } - - if (throwable is Error) { - stackTrace ??= throwable.stackTrace; - } - stackTrace ??= _options - .exceptionStackTraceExtractor(throwable.runtimeType) - ?.stackTrace(throwable); - - // throwable.stackTrace is null if its an exception that was never thrown - // hence we check again if stackTrace is null and if not, read the current stack trace - // but only if attachStacktrace is enabled - - if (_options.attachStacktrace) { - if (stackTrace == null || stackTrace == StackTrace.empty) { - snapshot = true; - stackTrace = getCurrentStackTrace(); - removeSentryFrames = true; - } - } - - SentryStackTrace? sentryStackTrace; - if (stackTrace != null) { - sentryStackTrace = _stacktraceFactory.parse(stackTrace, - removeSentryFrames: removeSentryFrames); - sentryStackTrace.snapshot = snapshot; - if (sentryStackTrace.frames.isEmpty) { - sentryStackTrace = null; - } - } - - final throwableString = throwable.toString(); - final stackTraceString = stackTrace.toString(); - final value = throwableString.replaceAll(stackTraceString, '').trim(); - - String errorTypeName = throwable.runtimeType.toString(); - - if (_options.enableExceptionTypeIdentification) { - for (final errorTypeIdentifier in _options.exceptionTypeIdentifiers) { - final identifiedErrorType = errorTypeIdentifier.identifyType(throwable); - if (identifiedErrorType != null) { - errorTypeName = identifiedErrorType; - break; - } - } - } - - // if --obfuscate feature is enabled, 'type' won't be human readable. - // https://flutter.dev/docs/deployment/obfuscate#caveat - return SentryException( - type: errorTypeName, - value: value.isNotEmpty ? value : null, - mechanism: mechanism, - stackTrace: sentryStackTrace, - throwable: throwable, - ); - } -} diff --git a/dart/lib/src/sentry_isolate.dart b/dart/lib/src/sentry_isolate.dart deleted file mode 100644 index 89b3e38ed2..0000000000 --- a/dart/lib/src/sentry_isolate.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'dart:isolate'; -import 'package:meta/meta.dart'; - -import 'throwable_mechanism.dart'; -import 'protocol.dart'; -import 'hub.dart'; -import 'hub_adapter.dart'; - -/// Conveniently spawn an isolate with an attached sentry error listener. -class SentryIsolate { - /// Calls [Isolate.spawn] with an error listener from the Sentry SDK. - /// - /// Providing your own `onError` will not add the listener from Sentry SDK. - static Future spawn( - void Function(T message) entryPoint, T message, - {bool paused = false, - bool errorsAreFatal = true, - SendPort? onExit, - SendPort? onError, - String? debugName, - @internal Hub? hub}) async { - return Isolate.spawn( - entryPoint, - message, - paused: paused, - errorsAreFatal: errorsAreFatal, - onExit: onExit, - onError: onError ?? createPort(hub ?? HubAdapter()).sendPort, - debugName: debugName, - ); - } - - @internal - static RawReceivePort createPort(Hub hub) { - return RawReceivePort( - (dynamic error) async { - await handleIsolateError(hub, error); - }, - ); - } - - @visibleForTesting - - /// Parse and raise an event out of the Isolate error. - static Future handleIsolateError( - Hub hub, - dynamic error, - ) async { - hub.options.log(SentryLevel.debug, 'Capture from IsolateError $error'); - - // https://api.dartlang.org/stable/2.7.0/dart-isolate/Isolate/addErrorListener.html - // error is a list of 2 elements - if (error is List && error.length == 2) { - /// The errors are sent back as two-element lists. - /// The first element is a `String` representation of the error, usually - /// created by calling `toString` on the error. - /// The second element is a `String` representation of an accompanying - /// stack trace, or `null` if no stack trace was provided. - /// To convert this back to a [StackTrace] object, use [StackTrace.fromString]. - final String throwable = error.first; - final String? stackTrace = error.last; - - hub.options.log( - SentryLevel.error, - 'Uncaught isolate error', - logger: 'sentry.isolateError', - exception: throwable, - stackTrace: - stackTrace == null ? null : StackTrace.fromString(stackTrace), - ); - - // Isolate errors don't crash the app, but is not handled by the user. - final mechanism = Mechanism(type: 'isolateError', handled: false); - final throwableMechanism = ThrowableMechanism(mechanism, throwable); - - final event = SentryEvent( - throwable: throwableMechanism, - level: hub.options.markAutomaticallyCollectedErrorsAsFatal - ? SentryLevel.fatal - : SentryLevel.error, - timestamp: hub.options.clock(), - ); - - // marks the span status if none to `internal_error` in case there's an - // unhandled error - hub.configureScope( - (scope) => scope.span?.status ??= const SpanStatus.internalError(), - ); - - await hub.captureEvent(event, stackTrace: stackTrace); - } - } -} diff --git a/dart/lib/src/sentry_isolate_extension.dart b/dart/lib/src/sentry_isolate_extension.dart deleted file mode 100644 index 845fd99ce0..0000000000 --- a/dart/lib/src/sentry_isolate_extension.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'dart:isolate'; - -import 'package:meta/meta.dart'; - -import 'sentry_isolate.dart'; -import 'hub.dart'; -import 'hub_adapter.dart'; - -/// Record isolate errors with the Sentry SDK. -extension SentryIsolateExtension on Isolate { - /// Calls [addErrorListener] with an error listener from the Sentry SDK. Store - /// the returned [RawReceivePort] if you want to remove the Sentry listener - /// again. - /// - /// Since isolates run concurrently, it's possible for it to exit before the - /// error listener is established. To avoid this, start the isolate paused, - /// add the listener and then resume the isolate. - RawReceivePort addSentryErrorListener({@internal Hub? hub}) { - final port = SentryIsolate.createPort(hub ?? HubAdapter()); - addErrorListener(port.sendPort); - return port; - } - - /// Pass the [receivePort] returned from [addSentryErrorListener] to remove - /// the sentry error listener. - void removeSentryErrorListener(RawReceivePort receivePort) { - removeErrorListener(receivePort.sendPort); - } -} diff --git a/dart/lib/src/sentry_item_type.dart b/dart/lib/src/sentry_item_type.dart deleted file mode 100644 index c712ad8793..0000000000 --- a/dart/lib/src/sentry_item_type.dart +++ /dev/null @@ -1,10 +0,0 @@ -class SentryItemType { - static const String event = 'event'; - static const String attachment = 'attachment'; - static const String transaction = 'transaction'; - static const String clientReport = 'client_report'; - static const String profile = 'profile'; - static const String statsd = 'statsd'; - static const String log = 'log'; - static const String unknown = '__unknown__'; -} diff --git a/dart/lib/src/sentry_log_batcher.dart b/dart/lib/src/sentry_log_batcher.dart deleted file mode 100644 index d5f023f979..0000000000 --- a/dart/lib/src/sentry_log_batcher.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'dart:async'; -import 'sentry_envelope.dart'; -import 'sentry_options.dart'; -import 'protocol/sentry_log.dart'; -import 'package:meta/meta.dart'; - -@internal -class SentryLogBatcher { - SentryLogBatcher(this._options, {Duration? flushTimeout, int? maxBufferSize}) - : _flushTimeout = flushTimeout ?? Duration(seconds: 5), - _maxBufferSize = maxBufferSize ?? 100; - - final SentryOptions _options; - final Duration _flushTimeout; - final int _maxBufferSize; - - final _logBuffer = []; - - Timer? _flushTimer; - - void addLog(SentryLog log) { - _logBuffer.add(log); - - _flushTimer?.cancel(); - - if (_logBuffer.length >= _maxBufferSize) { - return flush(); - } else { - _flushTimer = Timer(_flushTimeout, flush); - } - } - - void flush() { - _flushTimer?.cancel(); - _flushTimer = null; - - final logs = List.from(_logBuffer); - _logBuffer.clear(); - - if (logs.isEmpty) { - return; - } - - final envelope = SentryEnvelope.fromLogs( - logs, - _options.sdk, - ); - - // TODO: Make sure the Android SDK understands the log envelope type. - _options.transport.send(envelope); - } -} diff --git a/dart/lib/src/sentry_logger.dart b/dart/lib/src/sentry_logger.dart deleted file mode 100644 index 930b78166e..0000000000 --- a/dart/lib/src/sentry_logger.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'dart:async'; -import 'hub.dart'; -import 'hub_adapter.dart'; -import 'protocol/sentry_log.dart'; -import 'protocol/sentry_log_level.dart'; -import 'protocol/sentry_log_attribute.dart'; -import 'sentry_options.dart'; -import 'sentry_logger_formatter.dart'; - -class SentryLogger { - SentryLogger(this._clock, {Hub? hub}) : _hub = hub ?? HubAdapter(); - - final ClockProvider _clock; - final Hub _hub; - - late final fmt = SentryLoggerFormatter(this); - - FutureOr trace( - String body, { - Map? attributes, - }) { - return _captureLog(SentryLogLevel.trace, body, attributes: attributes); - } - - FutureOr debug( - String body, { - Map? attributes, - }) { - return _captureLog(SentryLogLevel.debug, body, attributes: attributes); - } - - FutureOr info( - String body, { - Map? attributes, - }) { - return _captureLog(SentryLogLevel.info, body, attributes: attributes); - } - - FutureOr warn( - String body, { - Map? attributes, - }) { - return _captureLog(SentryLogLevel.warn, body, attributes: attributes); - } - - FutureOr error( - String body, { - Map? attributes, - }) { - return _captureLog(SentryLogLevel.error, body, attributes: attributes); - } - - FutureOr fatal( - String body, { - Map? attributes, - }) { - return _captureLog(SentryLogLevel.fatal, body, attributes: attributes); - } - - // Helper - - FutureOr _captureLog( - SentryLogLevel level, - String body, { - Map? attributes, - }) { - final log = SentryLog( - timestamp: _clock(), - level: level, - body: body, - attributes: attributes ?? {}, - ); - return _hub.captureLog(log); - } -} diff --git a/dart/lib/src/sentry_logger_formatter.dart b/dart/lib/src/sentry_logger_formatter.dart deleted file mode 100644 index de5ebcca08..0000000000 --- a/dart/lib/src/sentry_logger_formatter.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'dart:async'; -import 'protocol/sentry_log_attribute.dart'; -import 'sentry_template_string.dart'; -import 'sentry_logger.dart'; - -class SentryLoggerFormatter { - SentryLoggerFormatter(this._logger); - - final SentryLogger _logger; - - FutureOr trace( - String templateBody, - List arguments, { - Map? attributes, - }) { - return _format( - templateBody, - arguments, - attributes, - (formattedBody, allAttributes) { - return _logger.trace(formattedBody, attributes: allAttributes); - }, - ); - } - - FutureOr debug( - String templateBody, - List arguments, { - Map? attributes, - }) { - return _format( - templateBody, - arguments, - attributes, - (formattedBody, allAttributes) { - return _logger.debug(formattedBody, attributes: allAttributes); - }, - ); - } - - FutureOr info( - String templateBody, - List arguments, { - Map? attributes, - }) { - return _format( - templateBody, - arguments, - attributes, - (formattedBody, allAttributes) { - return _logger.info(formattedBody, attributes: allAttributes); - }, - ); - } - - FutureOr warn( - String templateBody, - List arguments, { - Map? attributes, - }) { - return _format( - templateBody, - arguments, - attributes, - (formattedBody, allAttributes) { - return _logger.warn(formattedBody, attributes: allAttributes); - }, - ); - } - - FutureOr error( - String templateBody, - List arguments, { - Map? attributes, - }) { - return _format( - templateBody, - arguments, - attributes, - (formattedBody, allAttributes) { - return _logger.error(formattedBody, attributes: allAttributes); - }, - ); - } - - FutureOr fatal( - String templateBody, - List arguments, { - Map? attributes, - }) { - return _format( - templateBody, - arguments, - attributes, - (formattedBody, allAttributes) { - return _logger.fatal(formattedBody, attributes: allAttributes); - }, - ); - } - - // Helper - - FutureOr _format( - String templateBody, - List arguments, - Map? attributes, - FutureOr Function(String, Map) callback, - ) { - final templateString = SentryTemplateString(templateBody, arguments); - final formattedBody = templateString.format(); - final templateAttributes = _getAllAttributes(templateBody, arguments); - if (attributes != null) { - templateAttributes.addAll(attributes); - } - return callback(formattedBody, templateAttributes); - } - - Map _getAllAttributes( - String templateBody, - List args, - ) { - final templateAttributes = { - 'sentry.message.template': SentryLogAttribute.string(templateBody), - }; - for (var i = 0; i < args.length; i++) { - final argument = args[i]; - final key = 'sentry.message.parameter.$i'; - if (argument is String) { - templateAttributes[key] = SentryLogAttribute.string(argument); - } else if (argument is int) { - templateAttributes[key] = SentryLogAttribute.int(argument); - } else if (argument is bool) { - templateAttributes[key] = SentryLogAttribute.bool(argument); - } else if (argument is double) { - templateAttributes[key] = SentryLogAttribute.double(argument); - } else { - try { - templateAttributes[key] = - SentryLogAttribute.string(argument.toString()); - } catch (e) { - templateAttributes[key] = SentryLogAttribute.string(""); - } - } - } - return templateAttributes; - } -} diff --git a/dart/lib/src/sentry_measurement.dart b/dart/lib/src/sentry_measurement.dart deleted file mode 100644 index 965bd56272..0000000000 --- a/dart/lib/src/sentry_measurement.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'sentry_measurement_unit.dart'; - -class SentryMeasurement { - SentryMeasurement( - this.name, - this.value, { - this.unit, - }); - - static const totalFramesName = 'frames_total'; - static const slowFramesName = 'frames_slow'; - static const frozenFramesName = 'frames_frozen'; - static const framesDelayName = 'frames_delay'; - - /// Amount of frames drawn during a transaction - SentryMeasurement.totalFrames(this.value) - : name = totalFramesName, - unit = SentryMeasurementUnit.none; - - /// Amount of slow frames drawn during a transaction. - /// A slow frame is any frame longer than 1s / refreshrate. - /// So for example any frame slower than 16ms for a refresh rate of 60hz. - SentryMeasurement.slowFrames(this.value) - : name = slowFramesName, - unit = SentryMeasurementUnit.none; - - /// Amount of frozen frames drawn during a transaction. - /// Typically defined as frames slower than 500ms. - SentryMeasurement.frozenFrames(this.value) - : name = frozenFramesName, - unit = SentryMeasurementUnit.none; - - /// Total duration of frames delayed. - SentryMeasurement.framesDelay(this.value) - : name = framesDelayName, - unit = SentryMeasurementUnit.none; - - /// Duration of the Cold App start in milliseconds - SentryMeasurement.coldAppStart(Duration duration) - : assert(!duration.isNegative), - name = 'app_start_cold', - value = duration.inMilliseconds, - unit = DurationSentryMeasurementUnit.milliSecond; - - /// Duration of the Warm App start in milliseconds - SentryMeasurement.warmAppStart(Duration duration) - : assert(!duration.isNegative), - name = 'app_start_warm', - value = duration.inMilliseconds, - unit = DurationSentryMeasurementUnit.milliSecond; - - /// Duration of the time to initial display in milliseconds - SentryMeasurement.timeToInitialDisplay(Duration duration) - : assert(!duration.isNegative), - name = 'time_to_initial_display', - value = duration.inMilliseconds, - unit = DurationSentryMeasurementUnit.milliSecond; - - /// Duration of the time to full display in milliseconds - SentryMeasurement.timeToFullDisplay(Duration duration) - : assert(!duration.isNegative), - name = 'time_to_full_display', - value = duration.inMilliseconds, - unit = DurationSentryMeasurementUnit.milliSecond; - - final String name; - final num value; - final SentryMeasurementUnit? unit; - - Map toJson() { - return { - 'value': value, - if (unit != null) 'unit': unit?.toStringValue(), - }; - } -} diff --git a/dart/lib/src/sentry_measurement_unit.dart b/dart/lib/src/sentry_measurement_unit.dart deleted file mode 100644 index 96376a14f9..0000000000 --- a/dart/lib/src/sentry_measurement_unit.dart +++ /dev/null @@ -1,122 +0,0 @@ -/// The unit of measurement of a metric value. -/// Units augment metric values by giving them a magnitude and semantics. -/// Units and their precisions are uniquely represented by a string identifier. -abstract class SentryMeasurementUnit { - static final none = NoneSentryMeasurementUnit(); - String get name; -} - -extension SentryMeasurementUnitExtension on SentryMeasurementUnit { - String toStringValue() { - return name; - } -} - -enum DurationSentryMeasurementUnit implements SentryMeasurementUnit { - /// Nanosecond (`"nanosecond"`), 10^-9 seconds. - nanoSecond('nanosecond'), - - /// Microsecond (`"microsecond"`), 10^-6 seconds. - microSecond('microsecond'), - - /// Millisecond (`"millisecond"`), 10^-3 seconds. - milliSecond('millisecond'), - - /// Full second (`"second"`). - second('second'), - - /// Minute (`"minute"`), 60 seconds. - minute('minute'), - - /// Hour (`"hour"`), 3600 seconds. - hour('hour'), - - /// Day (`"day"`), 86,400 seconds. - day('day'), - - /// Week (`"week"`), 604,800 seconds. - week('week'); - - const DurationSentryMeasurementUnit(this.name); - - @override - final String name; -} - -enum InformationSentryMeasurementUnit implements SentryMeasurementUnit { - /// Bit (`"bit"`), corresponding to 1/8 of a byte. - bit("bit"), - - /// Byte (`"byte"`). - byte('byte'), - - /// Kilobyte (`"kilobyte"`), 10^3 bytes. - kiloByte('kilobyte'), - - /// Kibibyte (`"kibibyte"`), 2^10 bytes. - kibiByte('kibibyte'), - - /// Megabyte (`"megabyte"`), 10^6 bytes. - megaByte('megabyte'), - - /// Mebibyte (`"mebibyte"`), 2^20 bytes. - mebiByte('mebibyte'), - - /// Gigabyte (`"gigabyte"`), 10^9 bytes. - gigaByte('gigabyte'), - - /// Gibibyte (`"gibibyte"`), 2^30 bytes. - gibiByte('gibibyte'), - - /// Terabyte (`"terabyte"`), 10^12 bytes. - teraByte('terabyte'), - - /// Tebibyte (`"tebibyte"`), 2^40 bytes. - tebiByte('tebibyte'), - - /// Petabyte (`"petabyte"`), 10^15 bytes. - petaByte('petabyte'), - - /// Pebibyte (`"pebibyte"`), 2^50 bytes. - pebiByte('pebibyte'), - - /// Exabyte (`"exabyte"`), 10^18 bytes. - exaByte('exabyte'), - - /// Exbibyte (`"exbibyte"`), 2^60 bytes. - exbiByte('exbibyte'); - - const InformationSentryMeasurementUnit(this.name); - - @override - final String name; -} - -enum FractionSentryMeasurementUnit implements SentryMeasurementUnit { - /// Floating point fraction of `1`. - ratio('ratio'), - - /// Ratio expressed as a fraction of `100`. `100%` equals a ratio of `1.0`. - percent('percent'); - - const FractionSentryMeasurementUnit(this.name); - - @override - final String name; -} - -/// Custom units without builtin conversion. No formatting will be applied to -/// the measurement value in the Sentry product, and the value with the unit -/// will be shown as is. -class CustomSentryMeasurementUnit implements SentryMeasurementUnit { - CustomSentryMeasurementUnit(this.name); - - @override - final String name; -} - -/// Untyped value. -class NoneSentryMeasurementUnit implements SentryMeasurementUnit { - @override - String get name => 'none'; -} diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart deleted file mode 100644 index 7aba9721d8..0000000000 --- a/dart/lib/src/sentry_options.dart +++ /dev/null @@ -1,682 +0,0 @@ -import 'dart:async'; - -import 'package:http/http.dart'; -import 'package:meta/meta.dart'; - -import '../sentry.dart'; -import 'client_reports/client_report_recorder.dart'; -import 'client_reports/noop_client_report_recorder.dart'; -import 'diagnostic_log.dart'; -import 'environment/environment_variables.dart'; -import 'noop_client.dart'; -import 'platform/platform.dart'; -import 'sentry_exception_factory.dart'; -import 'sentry_stack_trace_factory.dart'; -import 'transport/noop_transport.dart'; -import 'version.dart'; -import 'sentry_log_batcher.dart'; -import 'noop_log_batcher.dart'; -import 'dart:developer' as developer; - -// TODO: shutdownTimeout, flushTimeoutMillis -// https://api.dart.dev/stable/2.10.2/dart-io/HttpClient/close.html doesn't have a timeout param, we'd need to implement manually - -/// Sentry SDK options -class SentryOptions { - /// Default Log level if not specified Default is WARNING - static final SentryLevel _defaultDiagnosticLevel = SentryLevel.warning; - - String? _dsn; - Dsn? _parsedDsn; - - /// The DSN tells the SDK where to send the events to. - /// If an empty string is used, the SDK will not send any events. - String? get dsn => _dsn; - - set dsn(String? value) { - if (_dsn != value) { - _dsn = value; - _parsedDsn = null; // Invalidate the cached parsed DSN - } - } - - /// Evaluates and parses the DSN. - /// May throw an exception if the DSN is invalid. - @internal - Dsn get parsedDsn { - _parsedDsn ??= _parseDsn(); - return _parsedDsn!; - } - - Dsn _parseDsn() { - if (_dsn == null || _dsn!.isEmpty) { - throw StateError('DSN is null or empty'); - } - return Dsn.parse(_dsn!); - } - - /// If [compressPayload] is `true` the outgoing HTTP payloads are compressed - /// using gzip. Otherwise, the payloads are sent in plain UTF8-encoded JSON - /// text. The compression is enabled by default. - bool compressPayload = true; - - /// If [httpClient] is provided, it is used instead of the default client to - /// make HTTP calls to Sentry.io. This is useful in tests. - /// If you don't need to send events, use [NoOpClient]. - Client httpClient = NoOpClient(); - - /// If [clock] is provided, it is used to get time instead of the system - /// clock. This is useful in tests. Should be an implementation of [ClockProvider]. - /// The ClockProvider is expected to return UTC time. - @internal - ClockProvider clock = getUtcDateTime; - - int _maxBreadcrumbs = 100; - - /// This variable controls the total amount of breadcrumbs that should be captured Default is 100 - int get maxBreadcrumbs => _maxBreadcrumbs; - - set maxBreadcrumbs(int maxBreadcrumbs) { - assert(maxBreadcrumbs >= 0); - _maxBreadcrumbs = maxBreadcrumbs; - } - - /// Initial value of 20 MiB according to - /// https://develop.sentry.dev/sdk/features/#max-attachment-size - int _maxAttachmentSize = 20 * 1024 * 1024; - - /// Maximum allowed file size of attachments, in bytes. - /// Attachments above this size will be discarded - /// - /// Remarks: Regardless of this setting, attachments are also limited to 20mb - /// (compressed) on Relay. - int get maxAttachmentSize => _maxAttachmentSize; - - set maxAttachmentSize(int maxAttachmentSize) { - assert(maxAttachmentSize > 0); - _maxAttachmentSize = maxAttachmentSize; - } - - /// Maximum number of spans that can be attached to single transaction. - int _maxSpans = 1000; - - /// Returns the maximum number of spans that can be attached to single transaction. - int get maxSpans => _maxSpans; - - /// Sets the maximum number of spans that can be attached to single transaction. - set maxSpans(int maxSpans) { - assert(maxSpans > 0); - _maxSpans = maxSpans; - } - - int _maxQueueSize = 30; - - /// Returns the max number of events Sentry will send when calling capture - /// methods in a tight loop. Default is 30. - int get maxQueueSize => _maxQueueSize; - - /// Sets how many unawaited events can be sent by Sentry. (e.g. capturing - /// events in a tight loop) at once. If you need to send more, please use the - /// await keyword. - set maxQueueSize(int count) { - assert(count > 0); - _maxQueueSize = count; - } - - /// Configures up to which size request bodies should be included in events. - /// This does not change whether an event is captured. - MaxRequestBodySize maxRequestBodySize = MaxRequestBodySize.never; - - SdkLogCallback _log = noOpLog; - - /// Log callback to log useful debugging information if debug is enabled - SdkLogCallback get log => _log; - - @internal - set log(SdkLogCallback value) { - diagnosticLog = DiagnosticLog(value, this); - _log = diagnosticLog!.log; - } - - @visibleForTesting - DiagnosticLog? diagnosticLog; - - final List _eventProcessors = []; - - /// Are callbacks that run for every event. They can either return a new event which in most cases - /// means just adding data OR return null in case the event will be dropped and not sent. - /// - /// Global Event processors are executed after the Scope's processors - List get eventProcessors => - List.unmodifiable(_eventProcessors); - - final List _integrations = []; - - /// Code that provides middlewares, bindings or hooks into certain frameworks or environments, - /// along with code that inserts those bindings and activates them. - List get integrations => List.unmodifiable(_integrations); - - /// Turns debug mode on or off. If debug is enabled SDK will attempt to print out useful debugging - /// information if something goes wrong. Default is enabled in debug mode, otherwise it is disabled. - bool get debug => _debug; - - set debug(bool newValue) { - _debug = newValue; - if (_debug == true && - (log == noOpLog || diagnosticLog?.logger == noOpLog)) { - log = debugLog; - } - if (_debug == false && - (log == debugLog || diagnosticLog?.logger == debugLog)) { - log = noOpLog; - } - } - - bool _debug = false; - - /// minimum LogLevel to be used if debug is enabled - SentryLevel diagnosticLevel = _defaultDiagnosticLevel; - - /// Sentry client name used for the HTTP authHeader and userAgent eg - /// sentry.{language}.{platform}/{version} eg sentry.java.android/2.0.0 would be a valid case - String get sentryClientName => '${sdk.name}/${sdk.version}'; - - /// This function is called with an SDK specific event object and can return a modified event - /// object or nothing to skip reporting the event - BeforeSendCallback? beforeSend; - - /// This function is called with an SDK specific transaction object and can return a modified - /// transaction object or nothing to skip reporting the transaction - BeforeSendTransactionCallback? beforeSendTransaction; - - /// This function is called with an SDK specific feedback event object and can return a modified - /// feedback event object or nothing to skip reporting the feedback event - BeforeSendCallback? beforeSendFeedback; - - /// This function is called with an SDK specific breadcrumb object before the breadcrumb is added - /// to the scope. When nothing is returned from the function, the breadcrumb is dropped - BeforeBreadcrumbCallback? beforeBreadcrumb; - - /// This function is called right before a metric is about to be emitted. - /// Can return true to emit the metric, or false to drop it. - BeforeMetricCallback? beforeMetricCallback; - - /// This function is called right before a log is about to be sent. - /// Can return a modified log or null to drop the log. - BeforeSendLogCallback? beforeSendLog; - - /// Sets the release. SDK will try to automatically configure a release out of the box - /// See [docs for further information](https://docs.sentry.io/platforms/flutter/configuration/releases/) - String? release; - - /// Sets the environment. This string is freeform and not set by default. A release can be - /// associated with more than one environment to separate them in the UI Think staging vs prod or - /// similar. - /// See [docs for further information](https://docs.sentry.io/platforms/flutter/configuration/environments/) - String? environment; - - /// Configures the sample rate as a percentage of events to be sent in the range of 0.0 to 1.0. if - /// 1.0 is set it means that 100% of events are sent. If set to 0.1 only 10% of events will be - /// sent. Events are picked randomly. Default is null (disabled) - double? sampleRate; - - /// The ignoreErrors tells the SDK which errors should be not sent to the sentry server. - /// If an null or an empty list is used, the SDK will send all transactions. - /// To use regex add the `^` and the `$` to the string. - List ignoreErrors = []; - - /// The ignoreTransactions tells the SDK which transactions should be not sent to the sentry server. - /// If null or an empty list is used, the SDK will send all transactions. - /// To use regex add the `^` and the `$` to the string. - List ignoreTransactions = []; - - final List _inAppExcludes = []; - - /// A list of string prefixes of packages names that do not belong to the app, but rather third-party - /// packages. Packages considered not to be part of the app will be hidden from stack traces by - /// default. - /// example : `['sentry']` will exclude exception from `package:sentry/sentry.dart` - List get inAppExcludes => List.unmodifiable(_inAppExcludes); - - final List _inAppIncludes = []; - - /// A list of string prefixes of packages names that belong to the app. This option takes precedence - /// over inAppExcludes. - /// example: `['sentry']` will include exception from `package:sentry/sentry.dart` - List get inAppIncludes => List.unmodifiable(_inAppIncludes); - - /// Configures whether stack trace frames are considered in app frames by default. - /// You can use this to essentially make [inAppIncludes] or [inAppExcludes] - /// an allow or deny list. - /// This value is only used if Sentry can not find the origin of the frame. - /// - /// - If [considerInAppFramesByDefault] is true you only need to maintain - /// [inAppExcludes]. - /// - If [considerInAppFramesByDefault] is false you only need to maintain - /// [inAppIncludes]. - bool considerInAppFramesByDefault = true; - - /// The transport is an internal construct of the client that abstracts away the event sending. - Transport transport = NoOpTransport(); - - /// Sets the distribution. Think about it together with release and environment - String? dist; - - /// The server name used in the Sentry messages. - String? serverName; - - /// Sdk object that contains the Sentry Client Name and its version - late SdkVersion sdk; - - /// When enabled, stack traces are automatically attached to all messages logged. - /// Stack traces are always attached to exceptions; - /// however, when this option is set, stack traces are also sent with messages. - /// This option, for instance, means that stack traces appear next to all log messages. - /// - /// This option is `true` by default. - /// - /// Grouping in Sentry is different for events with stack traces and without. - /// As a result, you will get new groups as you enable or disable this flag for certain events. - bool attachStacktrace = true; - - /// Enable this option if you want to record calls to `print()` as - /// breadcrumbs. - /// In a Flutter environment, this setting also toggles recording of `debugPrint` calls. - /// `debugPrint` calls are only recorded in release builds, though. - bool enablePrintBreadcrumbs = true; - - /// If [runtimeChecker] is provided, it is used get the environment. - /// This is useful in tests. Should be an implementation of [RuntimeChecker]. - RuntimeChecker runtimeChecker = RuntimeChecker(); - - /// Info on which platform the SDK runs. - Platform platform = Platform(); - - /// If [environmentVariables] is provided, it is used get the environment - /// variables. This is useful in tests. - EnvironmentVariables environmentVariables = EnvironmentVariables.instance(); - - /// When enabled, the current isolate will be attached to the event. - /// This only applies to Dart:io platforms and only the current isolate. - /// The Dart runtime doesn't provide information about other active isolates. - /// - /// When running on web, this option has no effect at all. - /// - /// When running in the Flutter context, this enables attaching of threads - /// for native events, if supported for the native platform. - /// Currently, this is only supported on Android. - bool attachThreads = false; - - /// Whether to send personal identifiable information along with events - bool sendDefaultPii = false; - - /// Configures whether to record exceptions for failed requests. - /// Examples for captures exceptions are: - /// - In an browser environment this can be requests which fail because of CORS. - /// - In an mobile or desktop application this can be requests which failed - /// because the connection was interrupted. - /// Use with [SentryHttpClient] or `sentry_dio` integration for this to work, - /// or iOS native where it sets the value to `enableCaptureFailedRequests`. - bool captureFailedRequests = true; - - /// Whether to records requests as breadcrumbs. This is on by default. - /// It only has an effect when the SentryHttpClient or dio integration is in - /// use, or iOS native where it sets the value to `enableNetworkBreadcrumbs`. - bool recordHttpBreadcrumbs = true; - - /// Whether [SentryEvent] deduplication is enabled. - /// Can be further configured with [maxDeduplicationItems]. - /// Shoud be set to true if - /// [SentryHttpClient] is used to capture failed requests. - bool enableDeduplication = true; - - int _maxDeduplicationItems = 5; - - /// Describes how many exceptions are kept to be checked for deduplication. - /// This should be a small positiv integer in order to keep deduplication - /// performant. - /// Is only in effect if [enableDeduplication] is set to true. - int get maxDeduplicationItems => _maxDeduplicationItems; - - set maxDeduplicationItems(int count) { - assert(count > 0); - _maxDeduplicationItems = count; - } - - double? _tracesSampleRate; - - /// Returns the traces sample rate Default is null (disabled) - double? get tracesSampleRate => _tracesSampleRate; - - set tracesSampleRate(double? tracesSampleRate) { - assert(tracesSampleRate == null || - (tracesSampleRate >= 0 && tracesSampleRate <= 1)); - _tracesSampleRate = tracesSampleRate; - } - - /// This function is called by [TracesSamplerCallback] to determine if transaction is sampled - meant - /// to be sent to Sentry. - TracesSamplerCallback? tracesSampler; - - double? _profilesSampleRate; - - @internal // Only exposed by SentryFlutterOptions at the moment. - double? get profilesSampleRate => _profilesSampleRate; - - @internal // Only exposed by SentryFlutterOptions at the moment. - set profilesSampleRate(double? value) { - assert(value == null || (value >= 0 && value <= 1)); - _profilesSampleRate = value; - } - - /// Send statistics to sentry when the client drops events. - bool sendClientReports = true; - - /// If enabled, [scopeObservers] will be called when mutating scope. - bool enableScopeSync = true; - - final List _scopeObservers = []; - - List get scopeObservers => _scopeObservers; - - void addScopeObserver(ScopeObserver scopeObserver) { - _scopeObservers.add(scopeObserver); - } - - final List _ignoredExceptionsForType = []; - - /// Ignored exception types. - List get ignoredExceptionsForType => _ignoredExceptionsForType; - - /// Adds exception type to the list of ignored exceptions. - void addExceptionFilterForType(Type exceptionType) { - _ignoredExceptionsForType.add(exceptionType); - } - - /// Check if [ignoredExceptionsForType] contains an exception. - bool containsIgnoredExceptionForType(dynamic exception) { - return exception != null && - _ignoredExceptionsForType.contains(exception.runtimeType); - } - - /// Enables Dart symbolication for stack traces in Flutter for Android and Cocoa. - /// - /// If true, the SDK will attempt to symbolicate Dart stack traces when - /// [Sentry.init] is used instead of `SentryFlutter.init`. This is useful - /// when native debug images are not available. - /// - /// Automatically set to `false` when using `SentryFlutter.init` on a platform - /// with a native integration (e.g. Android, iOS, ...). - bool enableDartSymbolication = true; - - @internal - late ClientReportRecorder recorder = NoOpClientReportRecorder(); - - /// List of strings/regex controlling to which outgoing requests - /// the SDK will attach tracing headers. - /// - /// By default the SDK will attach those headers to all outgoing - /// requests. If this option is provided, the SDK will match the - /// request URL of outgoing requests against the items in this - /// array, and only attach tracing headers if a match was found. - final List tracePropagationTargets = ['.*']; - - /// The idle time to wait until the transaction will be finished. - /// The transaction will use the end timestamp of the last finished span as - /// the endtime for the transaction. - /// - /// When set to null the transaction must be finished manually. - /// - /// The default is 3 seconds. - Duration? idleTimeout = Duration(seconds: 3); - - final _causeExtractorsByType = {}; - - final _stackTraceExtractorsByType = {}; - - /// Returns a previously added [ExceptionCauseExtractor] by type - ExceptionCauseExtractor? exceptionCauseExtractor(Type type) { - return _causeExtractorsByType[type]; - } - - /// Adds [ExceptionCauseExtractor] in order to extract inner exceptions - void addExceptionCauseExtractor(ExceptionCauseExtractor extractor) { - _causeExtractorsByType[extractor.exceptionType] = extractor; - } - - /// Returns a previously added [ExceptionStackTraceExtractor] by type - ExceptionStackTraceExtractor? exceptionStackTraceExtractor(Type type) { - return _stackTraceExtractorsByType[type]; - } - - /// Adds [ExceptionStackTraceExtractor] in order to extract inner exceptions - void addExceptionStackTraceExtractor(ExceptionStackTraceExtractor extractor) { - _stackTraceExtractorsByType[extractor.exceptionType] = extractor; - } - - /// Only for internal use. Changed SDK behaviour when set to true: - /// - Rethrow exceptions that occur in user provided closures - @internal - bool automatedTestMode = false; - - /// Errors that the SDK automatically collects, for example in - /// [SentryIsolate], have `level` [SentryLevel.fatal] set per default. - /// Settings this to `false` will set the `level` to [SentryLevel.error]. - bool markAutomaticallyCollectedErrorsAsFatal = true; - - /// Enables identification of exception types in obfuscated builds. - /// When true, the SDK will attempt to identify common exception types - /// to improve readability of obfuscated issue titles. - /// - /// If you already have events with obfuscated issue titles this will change grouping. - /// - /// Default: `true` - bool enableExceptionTypeIdentification = true; - - final List _exceptionTypeIdentifiers = []; - - List get exceptionTypeIdentifiers => - List.unmodifiable(_exceptionTypeIdentifiers); - - void addExceptionTypeIdentifierByIndex( - int index, ExceptionTypeIdentifier exceptionTypeIdentifier) { - _exceptionTypeIdentifiers.insert( - index, exceptionTypeIdentifier.withCache()); - } - - /// Adds an exception type identifier to the beginning of the list. - /// This ensures it is processed first and takes precedence over existing identifiers. - void prependExceptionTypeIdentifier( - ExceptionTypeIdentifier exceptionTypeIdentifier) { - addExceptionTypeIdentifierByIndex(0, exceptionTypeIdentifier); - } - - /// The Spotlight configuration. - /// Disabled by default. - /// ```dart - /// spotlight = Spotlight(enabled: true) - /// ``` - Spotlight spotlight = Spotlight(enabled: false); - - /// Configure a proxy to use for SDK API calls. - /// - /// On io platforms without native SDKs (dart, linux, windows), this will use - /// an 'IOClient' with inner 'HTTPClient' for http communication. - /// A http proxy will be set in returned for 'HttpClient.findProxy' in the - /// form 'PROXY your_host:your_port'. - /// When setting 'user' and 'pass', the 'HttpClient.addProxyCredentials' - /// method will be called with empty 'realm'. - /// - /// On Android & iOS, the proxy settings are handled by the native SDK. - /// iOS only supports http proxies, while macOS also supports socks. - SentryProxy? proxy; - - /// Whether to group exceptions hierarchically. - /// - /// If true, exceptions will be grouped hierarchically if possible. - /// - /// This is opt-in, as it can lead to existing exception beeing grouped as new ones. - bool groupExceptions = false; - - /// Enable to capture and send logs to Sentry. - /// - /// Disabled by default. - bool enableLogs = false; - - /// Enables adding the module in [SentryStackFrame.module]. - /// This option only has an effect in non-obfuscated builds. - /// Enabling this option may change grouping. - bool includeModuleInStackTrace = false; - - late final SentryLogger logger = SentryLogger(clock); - - @internal - SentryLogBatcher logBatcher = NoopLogBatcher(); - - SentryOptions({String? dsn, Platform? platform, RuntimeChecker? checker}) { - this.dsn = dsn; - if (platform != null) { - this.platform = platform; - } - if (checker != null) { - runtimeChecker = checker; - } - sdk = SdkVersion(name: sdkName(this.platform.isWeb), version: sdkVersion); - sdk.addPackage('pub:sentry', sdkVersion); - } - - @internal - SentryOptions.empty() { - sdk = SdkVersion(name: 'noop', version: sdkVersion); - } - - /// Adds an event processor - void addEventProcessor(EventProcessor eventProcessor) { - _eventProcessors.add(eventProcessor); - } - - /// Removes an event processor - void removeEventProcessor(EventProcessor eventProcessor) { - _eventProcessors.remove(eventProcessor); - } - - /// Adds an integration - void addIntegration(Integration integration) { - _integrations.add(integration); - } - - /// Adds an integration in the given index - void addIntegrationByIndex(int index, Integration integration) { - _integrations.insert(index, integration); - } - - /// Removes an integration - void removeIntegration(Integration integration) { - _integrations.remove(integration); - } - - /// Adds an inAppExclude - void addInAppExclude(String inAppInclude) { - _inAppExcludes.add(inAppInclude); - } - - /// Adds an inAppIncludes - void addInAppInclude(String inAppExclude) { - _inAppIncludes.add(inAppExclude); - } - - /// Returns if tracing should be enabled. If tracing is disabled, starting transactions returns - /// [NoOpSentrySpan]. - bool isTracingEnabled() { - return tracesSampleRate != null || tracesSampler != null; - } - - List get performanceCollectors => - _performanceCollectors; - final List _performanceCollectors = []; - - void addPerformanceCollector(PerformanceCollector collector) { - _performanceCollectors.add(collector); - } - - @internal - late SentryExceptionFactory exceptionFactory = SentryExceptionFactory(this); - - @internal - late SentryStackTraceFactory stackTraceFactory = - SentryStackTraceFactory(this); - - @visibleForTesting - void debugLog( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - developer.log( - '[${level.name}] $message', - level: level.toDartLogLevel(), - name: logger ?? 'sentry', - time: clock(), - error: exception, - stackTrace: stackTrace, - ); - } -} - -@visibleForTesting -void noOpLog( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, -}) {} - -/// This function is called with an SDK specific event object and can return a modified event -/// object or nothing to skip reporting the event -typedef BeforeSendCallback = FutureOr Function( - SentryEvent event, - Hint hint, -); - -/// This function is called with an SDK specific transaction object and can return a modified transaction -/// object or nothing to skip reporting the transaction -typedef BeforeSendTransactionCallback = FutureOr Function( - SentryTransaction transaction, - Hint hint, -); - -/// This function is called with an SDK specific breadcrumb object before the breadcrumb is added -/// to the scope. When nothing is returned from the function, the breadcrumb is dropped -typedef BeforeBreadcrumbCallback = Breadcrumb? Function( - Breadcrumb? breadcrumb, - Hint hint, -); - -/// This function is called right before a metric is about to be emitted. -/// Can return true to emit the metric, or false to drop it. -typedef BeforeMetricCallback = bool Function( - String key, { - Map? tags, -}); - -/// This function is called right before a log is about to be sent. -/// Can return a modified log or null to drop the log. -typedef BeforeSendLogCallback = FutureOr Function(SentryLog log); - -/// Used to provide timestamp for logging. -typedef ClockProvider = DateTime Function(); - -/// Logger callback to log useful debugging information if debug is enabled -typedef SdkLogCallback = void Function( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, -}); - -typedef TracesSamplerCallback = double? Function( - SentrySamplingContext samplingContext); diff --git a/dart/lib/src/sentry_run_zoned_guarded.dart b/dart/lib/src/sentry_run_zoned_guarded.dart deleted file mode 100644 index d2f47fbef5..0000000000 --- a/dart/lib/src/sentry_run_zoned_guarded.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'dart:async'; - -import 'package:meta/meta.dart'; - -import '../sentry.dart'; - -@internal -class SentryRunZonedGuarded { - /// Needed to check if we somehow caused a `print()` recursion - static var _isPrinting = false; - - static R? sentryRunZonedGuarded( - Hub hub, - R Function() body, - void Function(Object error, StackTrace stack)? onError, { - Map? zoneValues, - ZoneSpecification? zoneSpecification, - }) { - final sentryOnError = (exception, stackTrace) async { - final options = hub.options; - await _captureError(hub, options, exception, stackTrace); - - if (onError != null) { - onError(exception, stackTrace); - } - }; - - final userPrint = zoneSpecification?.print; - - final sentryZoneSpecification = ZoneSpecification.from( - zoneSpecification ?? ZoneSpecification(), - print: (self, parent, zone, line) { - final options = hub.options; - - if (userPrint != null) { - userPrint(self, parent, zone, line); - } - - if (!options.enablePrintBreadcrumbs || !hub.isEnabled) { - // early bail out, in order to better guard against the recursion - // as described below. - parent.print(zone, line); - return; - } - if (_isPrinting) { - // We somehow landed in a recursion. - // This happens for example if: - // - hub.addBreadcrumb() called print() itself - // - This happens for example if hub.isEnabled == false and - // options.logger == _debugLogger - // - // Anyway, in order to not cause a stack overflow due to recursion - // we drop any further print() call while adding a breadcrumb. - parent.print( - zone, - 'Recursion during print() call.' - 'Abort adding print() call as Breadcrumb.', - ); - return; - } - - try { - _isPrinting = true; - unawaited(hub.addBreadcrumb( - Breadcrumb.console( - message: line, - level: SentryLevel.debug, - ), - )); - parent.print(zone, line); - } finally { - _isPrinting = false; - } - }, - ); - return runZonedGuarded( - body, - sentryOnError, - zoneValues: zoneValues, - zoneSpecification: sentryZoneSpecification, - ); - } - - static Future _captureError( - Hub hub, - SentryOptions options, - Object exception, - StackTrace stackTrace, - ) async { - options.log( - SentryLevel.error, - 'Uncaught zone error', - logger: 'sentry.runZonedGuarded', - exception: exception, - stackTrace: stackTrace, - ); - - // runZonedGuarded doesn't crash the app, but is not handled by the user. - final mechanism = Mechanism(type: 'runZonedGuarded', handled: false); - final throwableMechanism = ThrowableMechanism(mechanism, exception); - - final event = SentryEvent( - throwable: throwableMechanism, - level: options.markAutomaticallyCollectedErrorsAsFatal - ? SentryLevel.fatal - : SentryLevel.error, - timestamp: hub.options.clock(), - ); - - // marks the span status if none to `internal_error` in case there's an - // unhandled error - hub.configureScope( - (scope) => scope.span?.status ??= const SpanStatus.internalError(), - ); - - await hub.captureEvent(event, stackTrace: stackTrace); - } -} diff --git a/dart/lib/src/sentry_sampling_context.dart b/dart/lib/src/sentry_sampling_context.dart deleted file mode 100644 index d746e4e38e..0000000000 --- a/dart/lib/src/sentry_sampling_context.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'tracing.dart'; -import 'sentry_options.dart'; - -/// Context used by [TracesSamplerCallback] to determine if transaction -/// is going to be sampled. -@immutable -class SentrySamplingContext { - final SentryTransactionContext _transactionContext; - final Map _customSamplingContext; - - SentrySamplingContext(this._transactionContext, this._customSamplingContext); - - /// The Transaction context - SentryTransactionContext get transactionContext => _transactionContext; - - /// The given sampling context - Map get customSamplingContext => - Map.unmodifiable(_customSamplingContext); -} diff --git a/dart/lib/src/sentry_span_context.dart b/dart/lib/src/sentry_span_context.dart deleted file mode 100644 index aea17f646e..0000000000 --- a/dart/lib/src/sentry_span_context.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../sentry.dart'; - -class SentrySpanContext { - /// Determines which trace the Span belongs to - late SentryId traceId; - - /// Span id - late SpanId spanId; - - /// Id of a parent span - SpanId? parentSpanId; - - /// Short code identifying the type of operation the span is measuring - String operation; - - /// Longer description of the span's operation, which uniquely identifies the span but is - /// consistent across instances of the span. - String? description; - - /// The origin of the span indicates what created the span. - /// - /// Gets set by the SDK. It is not expected to be set manually by users. - /// - /// See https://develop.sentry.dev/sdk/performance/trace-origin - String? origin; - - /// Item encoded as JSON - Map toJson() { - return { - 'span_id': spanId.toString(), - 'trace_id': traceId.toString(), - 'op': operation, - if (parentSpanId != null) 'parent_span_id': parentSpanId.toString(), - if (description != null) 'description': description, - if (origin != null) 'origin': origin, - }; - } - - SentrySpanContext({ - SentryId? traceId, - SpanId? spanId, - this.parentSpanId, - required this.operation, - this.description, - this.origin, - }) : traceId = traceId ?? SentryId.newId(), - spanId = spanId ?? SpanId.newId(); - - @internal - SentryTraceContext toTraceContext({ - bool? sampled, - SpanStatus? status, - Map? data, - }) { - return SentryTraceContext( - operation: operation, - traceId: traceId, - spanId: spanId, - description: description, - parentSpanId: parentSpanId, - sampled: sampled, - status: status, - origin: origin, - data: data, - ); - } -} diff --git a/dart/lib/src/sentry_span_interface.dart b/dart/lib/src/sentry_span_interface.dart deleted file mode 100644 index a377310b79..0000000000 --- a/dart/lib/src/sentry_span_interface.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'hint.dart'; -import 'protocol.dart'; -import 'tracing.dart'; - -/// Represents performance monitoring Span. -abstract class ISentrySpan { - /// Starts a child Span. - ISentrySpan startChild( - String operation, { - String? description, - DateTime? startTimestamp, - }); - - /// Sets the tag on span or transaction. - void setTag(String key, String value); - - /// Removes the tag on span or transaction. - void removeTag(String key); - - /// Sets extra data on span or transaction. - void setData(String key, dynamic value); - - /// Removes extra data on span or transaction. - void removeData(String key); - - /// Sets span timestamp marking this span as finished. - Future finish({ - SpanStatus? status, - DateTime? endTimestamp, - Hint? hint, - }) async {} - - /// Gets span status. - SpanStatus? get status; - - /// Sets span status. - set status(SpanStatus? status); - - /// Gets the span context. - SentrySpanContext get context; - - /// Gets the span origin - String? get origin; - - /// Sets span origin. - /// - /// Gets set by the SDK. It is not expected to be set manually by users. - /// - /// See https://develop.sentry.dev/sdk/performance/trace-origin - set origin(String? origin); - - /// Returns the end timestamp if finished - DateTime? get endTimestamp; - - /// Returns the star timestamp - DateTime get startTimestamp; - - /// Returns true if span is finished - bool get finished; - - /// Returns the associated error - dynamic get throwable; - - /// Associated the error with the span - set throwable(dynamic throwable); - - @internal - SentryTracesSamplingDecision? get samplingDecision; - - /// Returns the trace information that could be sent as a sentry-trace header. - SentryTraceHeader toSentryTrace(); - - /// Set observed measurement for this span or transaction. - void setMeasurement( - String name, - num value, { - SentryMeasurementUnit? unit, - }); - - /// Returns the baggage that can be sent as "baggage" header. - SentryBaggageHeader? toBaggageHeader(); - - /// Returns the trace context. - SentryTraceContextHeader? traceContext(); - - @internal - void scheduleFinish(); -} diff --git a/dart/lib/src/sentry_stack_trace_factory.dart b/dart/lib/src/sentry_stack_trace_factory.dart deleted file mode 100644 index 2e89dd7c36..0000000000 --- a/dart/lib/src/sentry_stack_trace_factory.dart +++ /dev/null @@ -1,222 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:stack_trace/stack_trace.dart'; - -import 'origin.dart'; -import 'protocol.dart'; -import 'sentry_options.dart'; -import 'utils/stacktrace_utils.dart'; - -/// converts [StackTrace] to [SentryStackFrame]s -class SentryStackTraceFactory { - final SentryOptions _options; - - static final _frameRegex = RegExp(r'^\s*#', multiLine: true); - static final _baseAddrRegex = RegExp(r'isolate_dso_base[:=] *([A-Fa-f0-9]+)'); - static final SentryStackFrame _asynchronousGapFrameJson = - SentryStackFrame(absPath: ''); - - SentryStackTraceFactory(this._options); - - /// returns the [SentryStackFrame] list from a stackTrace ([StackTrace] or [String]) - @Deprecated('Use parse() instead') - List getStackFrames(dynamic stackTrace) { - return parse(stackTrace).frames; - } - - SentryStackTrace parse(dynamic stackTrace, {bool? removeSentryFrames}) { - final parsed = _parseStackTrace(stackTrace); - final frames = []; - var onlyAsyncGap = true; - - for (var t = 0; t < parsed.traces.length; t += 1) { - final trace = parsed.traces[t]; - - // NOTE: We want to keep the Sentry frames for SDK crash detection - // this does not affect grouping since they're not marked as inApp - // only exception if there was no stack trace, we remove them - for (final frame in trace.frames) { - var stackTraceFrame = encodeStackTraceFrame(frame); - - if (stackTraceFrame != null) { - if (removeSentryFrames == true && - (stackTraceFrame.package == 'sentry' || - stackTraceFrame.package == 'sentry_flutter')) { - continue; - } - frames.add(stackTraceFrame); - onlyAsyncGap = false; - } - } - - // fill asynchronous gap - if (t < parsed.traces.length - 1) { - frames.add(_asynchronousGapFrameJson); - } - } - - return SentryStackTrace( - frames: onlyAsyncGap ? [] : frames.reversed.toList(), - baseAddr: parsed.baseAddr, - buildId: parsed.buildId, - ); - } - - _StackInfo _parseStackTrace(dynamic stackTrace) { - if (stackTrace is Chain) { - return _StackInfo(stackTrace.traces); - } else if (stackTrace is Trace) { - return _StackInfo([stackTrace]); - } - - // We need to convert to string and split the headers manually, otherwise - // they end up in the final stack trace as "unparsed" lines. - // Note: [Chain.forTrace] would call [stackTrace.toString()] too. - if (stackTrace is StackTrace) { - stackTrace = stackTrace.toString(); - } - - if (stackTrace is String) { - // Remove headers (everything before the first line starting with '#'). - // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** - // pid: 19226, tid: 6103134208, name io.flutter.ui - // os: macos arch: arm64 comp: no sim: no - // build_id: 'bca64abfdfcc84d231bb8f1ccdbfbd8d' - // isolate_dso_base: 10fa20000, vm_dso_base: 10fa20000 - // isolate_instructions: 10fa27070, vm_instructions: 10fa21e20 - // #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 - // #01 abs 000000723d637527 _kDartIsolateSnapshotInstructions+0x1e5527 - - final startOffset = _frameRegex.firstMatch(stackTrace)?.start ?? 0; - final chain = Chain.parse( - startOffset == 0 ? stackTrace : stackTrace.substring(startOffset)); - final info = _StackInfo(chain.traces); - info.buildId = buildIdRegex.firstMatch(stackTrace)?.group(1); - info.baseAddr = _baseAddrRegex.firstMatch(stackTrace)?.group(1); - if (info.baseAddr != null) { - info.baseAddr = '0x${info.baseAddr}'; - } - return info; - } - return _StackInfo([]); - } - - /// converts [Frame] to [SentryStackFrame] - @visibleForTesting - SentryStackFrame? encodeStackTraceFrame(Frame frame) { - final member = frame.member; - - if (frame is UnparsedFrame && member != null) { - // if --split-debug-info is enabled, that's what we see: - // #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 - - // we are only interested on the #01, 02... items which contains the 'abs' addresses. - final match = absRegex.firstMatch(member); - if (match != null) { - return SentryStackFrame( - instructionAddr: '0x${match.group(1)!}', - // 'native' triggers the [LoadImageListIntegration] and server-side symbolication - platform: 'native', - ); - } - - // We shouldn't get here. If we do, it means there's likely an issue in - // the parsing so let's fall back and post a stack trace as is, so that at - // least we get an indication something's wrong and are able to fix it. - _options.log(SentryLevel.debug, "Failed to parse stack frame: $member"); - } - - final platform = _options.platform.isWeb ? 'javascript' : 'dart'; - final fileName = - frame.uri.pathSegments.isNotEmpty ? frame.uri.pathSegments.last : null; - final abs = '$eventOrigin${_absolutePathForCrashReport(frame)}'; - - final includeModule = - frame.package != null && _options.includeModuleInStackTrace; - - var sentryStackFrame = SentryStackFrame( - absPath: abs, - function: member, - // https://docs.sentry.io/development/sdk-dev/features/#in-app-frames - inApp: _isInApp(frame), - fileName: fileName, - package: frame.package, - platform: platform, - module: includeModule - ? frame.uri.pathSegments - .sublist(0, frame.uri.pathSegments.length - 1) - .join('/') - : null, - ); - - final line = frame.line; - if (line != null && line >= 0) { - sentryStackFrame.lineNo = frame.line; - } - - final column = frame.column; - if (column != null && column >= 0) { - sentryStackFrame.colNo = frame.column; - } - return sentryStackFrame; - } - - /// A stack frame's code path may be one of "file:", "dart:" and "package:". - /// - /// Absolute file paths may contain personally identifiable information, and - /// therefore are stripped to only send the base file name. For example, - /// "/foo/bar/baz.dart" is reported as "baz.dart". - /// - /// "dart:" and "package:" imports are always relative and are OK to send in - /// full. - String _absolutePathForCrashReport(Frame frame) { - if (frame.uri.scheme != 'dart' && - frame.uri.scheme != 'package' && - frame.uri.pathSegments.isNotEmpty) { - return frame.uri.pathSegments.last; - } - - return frame.uri.toString(); - } - - /// whether this frame comes from the app and not from Dart core or 3rd party librairies - bool _isInApp(Frame frame) { - final scheme = frame.uri.scheme; - - if (scheme.isEmpty) { - // Early bail out. - return _options.considerInAppFramesByDefault; - } - // The following code depends on the scheme being set. - - final package = frame.package; - if (package != null) { - if (_options.inAppIncludes.contains(package)) { - return true; - } - - if (_options.inAppExcludes.contains(package)) { - return false; - } - } - - if (frame.isCore) { - // This is a Dart frame - return false; - } - - if (frame.package == 'flutter') { - // This is a Flutter frame - return false; - } - - return _options.considerInAppFramesByDefault; - } -} - -class _StackInfo { - String? baseAddr; - String? buildId; - final List traces; - - _StackInfo(this.traces); -} diff --git a/dart/lib/src/sentry_template_string.dart b/dart/lib/src/sentry_template_string.dart deleted file mode 100644 index fa9c346443..0000000000 --- a/dart/lib/src/sentry_template_string.dart +++ /dev/null @@ -1,43 +0,0 @@ -class SentryTemplateString { - SentryTemplateString(this.template, this.arguments); - - final String template; - final List arguments; - static final _regex = RegExp(r'%(?:%|s)'); - - String format() { - assert(arguments.isNotEmpty, 'No arguments provided for template.'); - - int argIndex = 0; - var foundPlaceholders = false; - final string = template.replaceAllMapped(_regex, (Match m) { - final token = m[0]; - if (token == '%%') { - // `%%` → literal `%` - return '%'; - } - foundPlaceholders = true; - - // `%s` → next argument or empty if none left - if (argIndex < arguments.length) { - final value = arguments[argIndex++]; - try { - return value.toString(); - } catch (e) { - // If toString() fails, return empty string - return ''; - } - } - return ''; - }); - - assert(foundPlaceholders, 'No placeholders provided in template.'); - - return string; - } - - @override - String toString() { - return format(); - } -} diff --git a/dart/lib/src/sentry_trace_context_header.dart b/dart/lib/src/sentry_trace_context_header.dart deleted file mode 100644 index f94f772dc7..0000000000 --- a/dart/lib/src/sentry_trace_context_header.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'protocol/access_aware_map.dart'; -import 'protocol/sentry_id.dart'; -import 'sentry_baggage.dart'; -import 'sentry_options.dart'; - -class SentryTraceContextHeader { - SentryTraceContextHeader( - this.traceId, - this.publicKey, { - this.release, - this.environment, - this.userId, - this.transaction, - this.sampleRate, - this.sampleRand, - this.sampled, - this.unknown, - this.replayId, - }); - - final SentryId traceId; - final String publicKey; - final String? release; - final String? environment; - final String? userId; - final String? transaction; - final String? sampleRate; - final String? sampleRand; - final String? sampled; - - @internal - final Map? unknown; - - @internal - SentryId? replayId; - - /// Deserializes a [SentryTraceContextHeader] from JSON [Map]. - factory SentryTraceContextHeader.fromJson(Map data) { - final json = AccessAwareMap(data); - return SentryTraceContextHeader( - SentryId.fromId(json['trace_id']), - json['public_key'], - release: json['release'], - environment: json['environment'], - userId: json['user_id'], - transaction: json['transaction'], - sampleRate: json['sample_rate'], - sampled: json['sampled'], - replayId: - json['replay_id'] == null ? null : SentryId.fromId(json['replay_id']), - unknown: json.notAccessed(), - ); - } - - /// Produces a [Map] that can be serialized to JSON. - Map toJson() { - return { - ...?unknown, - 'trace_id': traceId.toString(), - 'public_key': publicKey, - if (release != null) 'release': release, - if (environment != null) 'environment': environment, - if (userId != null) 'user_id': userId, - if (transaction != null) 'transaction': transaction, - if (sampleRate != null) 'sample_rate': sampleRate, - if (sampled != null) 'sampled': sampled, - if (replayId != null) 'replay_id': replayId.toString(), - }; - } - - SentryBaggage toBaggage({ - SdkLogCallback? log, - }) { - final baggage = SentryBaggage({}, log: log); - baggage.setTraceId(traceId.toString()); - baggage.setPublicKey(publicKey); - - if (release != null) { - baggage.setRelease(release!); - } - if (environment != null) { - baggage.setEnvironment(environment!); - } - if (userId != null) { - baggage.setUserId(userId!); - } - if (transaction != null) { - baggage.setTransaction(transaction!); - } - if (sampleRate != null) { - baggage.setSampleRate(sampleRate!); - } - if (sampleRand != null) { - baggage.setSampleRand(sampleRand!); - } - if (sampled != null) { - baggage.setSampled(sampled!); - } - if (replayId != null) { - baggage.setReplayId(replayId.toString()); - } - return baggage; - } - - factory SentryTraceContextHeader.fromBaggage(SentryBaggage baggage) { - return SentryTraceContextHeader( - // TODO: implement and use proper get methods here - SentryId.fromId(baggage.get('sentry-trace_id').toString()), - baggage.get('sentry-public_key').toString(), - release: baggage.get('sentry-release'), - environment: baggage.get('sentry-environment'), - replayId: baggage.getReplayId(), - ); - } -} diff --git a/dart/lib/src/sentry_trace_origins.dart b/dart/lib/src/sentry_trace_origins.dart deleted file mode 100644 index 23359bf9f2..0000000000 --- a/dart/lib/src/sentry_trace_origins.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:meta/meta.dart'; - -@internal -class SentryTraceOrigins { - static const manual = 'manual'; - - static const autoNavigationRouteObserver = 'auto.navigation.route_observer'; - static const autoHttpHttp = 'auto.http.http'; - static const autoHttpDioHttpClientAdapter = - 'auto.http.dio.http_client_adapter'; - static const autoHttpDioTransformer = 'auto.http.dio.transformer'; - static const autoFile = 'auto.file'; - static const autoFileAssetBundle = 'auto.file.asset_bundle'; - static const autoDbSqfliteOpenDatabase = 'auto.db.sqflite.open_database'; - static const autoDbSqfliteBatch = 'auto.db.sqflite.batch'; - static const autoDbSqfliteDatabase = 'auto.db.sqflite.database'; - static const autoDbSqfliteDatabaseExecutor = - 'auto.db.sqflite.database_executor'; - static const autoDbSqfliteDatabaseFactory = - 'auto.db.sqflite.database_factory'; - static const autoDbIsar = 'auto.db.isar'; - static const autoDbIsarCollection = 'auto.db.isar.collection'; - static const autoDbHive = 'auto.db.hive'; - static const autoDbHiveBoxBase = 'auto.db.hive.box_base'; - static const autoDbHiveLazyBox = 'auto.db.hive.lazy_box'; - static const autoDbHiveBoxCollection = 'auto.db.hive.box_collection'; - static const autoDbDriftQueryInterceptor = 'auto.db.drift.query.interceptor'; - static const autoUiTimeToDisplay = 'auto.ui.time_to_display'; - static const manualUiTimeToDisplay = 'manual.ui.time_to_display'; -} diff --git a/dart/lib/src/sentry_tracer.dart b/dart/lib/src/sentry_tracer.dart deleted file mode 100644 index 1cc618a716..0000000000 --- a/dart/lib/src/sentry_tracer.dart +++ /dev/null @@ -1,438 +0,0 @@ -import 'dart:async'; - -import 'package:meta/meta.dart'; - -import '../sentry.dart'; -import 'profiling.dart'; -import 'sentry_tracer_finish_status.dart'; -import 'utils/sample_rate_format.dart'; - -@internal -class SentryTracer extends ISentrySpan { - final Hub _hub; - late bool _waitForChildren; - late String name; - - late final SentrySpan _rootSpan; - final List _children = []; - final Map _extra = {}; - - final Map _measurements = {}; - Map get measurements => _measurements; - - Timer? _autoFinishAfterTimer; - Duration? _autoFinishAfter; - - @visibleForTesting - Timer? get autoFinishAfterTimer => _autoFinishAfterTimer; - - OnTransactionFinish? _onFinish; - var _finishStatus = SentryTracerFinishStatus.notFinishing(); - late final bool _trimEnd; - - late SentryTransactionNameSource transactionNameSource; - - SentryTraceContextHeader? _sentryTraceContextHeader; - - // Profiler attached to this tracer. - late final SentryProfiler? profiler; - - // Resulting profile, after it has been collected. This is later used by - // SentryClient to attach as an envelope item when sending the transaction. - SentryProfileInfo? profileInfo; - - /// If [waitForChildren] is true, this transaction will not finish until all - /// its children are finished. - /// - /// When [autoFinishAfter] is provided, started transactions will - /// automatically be finished after this duration. - /// - /// If [trimEnd] is true, sets the end timestamp of the transaction to the - /// highest timestamp of child spans, trimming the duration of the - /// transaction. This is useful to discard extra time in the transaction that - /// is not accounted for in child spans, like what happens in the - /// [SentryNavigatorObserver](https://pub.dev/documentation/sentry_flutter/latest/sentry_flutter/SentryNavigatorObserver-class.html) - /// idle transactions, where we finish the transaction after a given - /// "idle time" and we don't want this "idle time" to be part of the transaction. - SentryTracer( - SentryTransactionContext transactionContext, - this._hub, { - DateTime? startTimestamp, - bool waitForChildren = false, - Duration? autoFinishAfter, - bool trimEnd = false, - OnTransactionFinish? onFinish, - this.profiler, - }) { - _rootSpan = SentrySpan( - this, - transactionContext, - _hub, - samplingDecision: transactionContext.samplingDecision, - startTimestamp: startTimestamp, - isRootSpan: true, - ); - _waitForChildren = waitForChildren; - _autoFinishAfter = autoFinishAfter; - - _scheduleTimer(); - name = transactionContext.name; - // always default to custom if not provided - transactionNameSource = transactionContext.transactionNameSource ?? - SentryTransactionNameSource.custom; - _trimEnd = trimEnd; - _onFinish = onFinish; - - for (final collector in _hub.options.performanceCollectors) { - if (collector is PerformanceContinuousCollector) { - collector.onSpanStarted(_rootSpan); - } - } - } - - @override - Future finish( - {SpanStatus? status, DateTime? endTimestamp, Hint? hint}) async { - final commonEndTimestamp = endTimestamp ?? _hub.options.clock(); - _autoFinishAfterTimer?.cancel(); - _finishStatus = SentryTracerFinishStatus.finishing(status); - if (_rootSpan.finished) { - return; - } - if (_waitForChildren && !_haveAllChildrenFinished()) { - return; - } - try { - _rootSpan.status ??= status; - - // remove span where its endTimestamp is before startTimestamp - _children.removeWhere( - (span) => !_hasSpanSuitableTimestamps(span, commonEndTimestamp)); - - var _rootEndTimestamp = commonEndTimestamp; - - // Trim the end timestamp of the transaction to the very last timestamp of child spans - if (_trimEnd && children.isNotEmpty) { - DateTime? latestEndTime; - - for (final child in children) { - final childEndTimestamp = child.endTimestamp; - if (childEndTimestamp != null) { - if (latestEndTime == null || - childEndTimestamp.isAfter(latestEndTime)) { - latestEndTime = child.endTimestamp; - } - } - } - - if (latestEndTime != null) { - _rootEndTimestamp = latestEndTime; - } - } - - // the callback should run before because if the span is finished, - // we cannot attach data, its immutable after being finished. - final finish = _onFinish?.call(this); - if (finish is Future) { - await finish; - } - await _rootSpan.finish(endTimestamp: _rootEndTimestamp, hint: hint); - - // remove from scope - await _hub.configureScope((scope) { - if (scope.span == this) { - scope.span = null; - } - }); - - // if it's an idle transaction which has no children, we drop it to save user's quota - if (children.isEmpty && _autoFinishAfter != null) { - return; - } - - final transaction = SentryTransaction(this); - transaction.measurements.addAll(_measurements); - - profileInfo = (status == null || status == SpanStatus.ok()) - ? await profiler?.finishFor(transaction) - : null; - - await _hub.captureTransaction( - transaction, - traceContext: traceContext(), - hint: hint, - ); - } finally { - profiler?.dispose(); - } - } - - @override - void removeData(String key) { - if (finished) { - return; - } - - _extra.remove(key); - } - - @override - void removeTag(String key) { - if (finished) { - return; - } - - _rootSpan.removeTag(key); - } - - @override - void setData(String key, dynamic value) { - if (finished) { - return; - } - - _extra[key] = value; - } - - @override - void setTag(String key, String value) { - if (finished) { - return; - } - - _rootSpan.setTag(key, value); - } - - @override - ISentrySpan startChild( - String operation, { - String? description, - DateTime? startTimestamp, - }) { - if (finished) { - return NoOpSentrySpan(); - } - - if (children.length >= _hub.options.maxSpans) { - _hub.options.log( - SentryLevel.warning, - 'Span operation: $operation, description: $description dropped due to limit reached. Returning NoOpSpan.', - ); - return NoOpSentrySpan(); - } - - return _rootSpan.startChild( - operation, - description: description, - startTimestamp: startTimestamp, - ); - } - - ISentrySpan startChildWithParentSpanId( - SpanId parentSpanId, - String operation, { - String? description, - DateTime? startTimestamp, - }) { - if (finished) { - return NoOpSentrySpan(); - } - - // reset the timer if a new child is added - _scheduleTimer(); - - if (children.length >= _hub.options.maxSpans) { - _hub.options.log( - SentryLevel.warning, - 'Span operation: $operation, description: $description dropped due to limit reached. Returning NoOpSpan.', - ); - return NoOpSentrySpan(); - } - - final context = SentrySpanContext( - traceId: _rootSpan.context.traceId, - parentSpanId: parentSpanId, - operation: operation, - description: description); - - final child = SentrySpan( - this, - context, - _hub, - samplingDecision: _rootSpan.samplingDecision, - startTimestamp: startTimestamp, - finishedCallback: _finishedCallback, - ); - - _children.add(child); - - for (final collector in _hub.options.performanceCollectors) { - if (collector is PerformanceContinuousCollector) { - collector.onSpanStarted(child); - } - } - - return child; - } - - Future _finishedCallback({ - DateTime? endTimestamp, - Hint? hint, - }) async { - final finishStatus = _finishStatus; - if (finishStatus.finishing) { - await finish( - status: finishStatus.status, - endTimestamp: endTimestamp, - hint: hint, - ); - } - } - - @override - SpanStatus? get status => _rootSpan.status; - - @override - SentrySpanContext get context => _rootSpan.context; - - @override - String? get origin => _rootSpan.origin; - - @override - set origin(String? origin) => _rootSpan.origin = origin; - - @override - DateTime get startTimestamp => _rootSpan.startTimestamp; - - @override - DateTime? get endTimestamp => _rootSpan.endTimestamp; - - Map get data => Map.unmodifiable(_extra); - - @override - bool get finished => _rootSpan.finished; - - List get children => _children; - - @override - dynamic get throwable => _rootSpan.throwable; - - @override - set throwable(throwable) => _rootSpan.throwable = throwable; - - @override - set status(SpanStatus? status) => _rootSpan.status = status; - - Map get tags => _rootSpan.tags; - - @override - SentryTraceHeader toSentryTrace() => _rootSpan.toSentryTrace(); - - bool _haveAllChildrenFinished() { - for (final child in children) { - if (!child.finished) { - return false; - } - } - return true; - } - - bool _hasSpanSuitableTimestamps( - SentrySpan span, DateTime endTimestampCandidate) => - !span.startTimestamp - .isAfter((span.endTimestamp ?? endTimestampCandidate)); - - @override - void setMeasurement(String name, num value, {SentryMeasurementUnit? unit}) { - if (finished) { - _hub.options.log(SentryLevel.debug, - "The tracer is already finished. Measurement $name cannot be set"); - return; - } - _measurements[name] = SentryMeasurement(name, value, unit: unit); - } - - void setMeasurementFromChild(String name, num value, - {SentryMeasurementUnit? unit}) { - // We don't want to overwrite span measurement, if it comes from a child. - if (!_measurements.containsKey(name)) { - setMeasurement(name, value, unit: unit); - } - } - - @override - SentryBaggageHeader? toBaggageHeader() { - final context = traceContext(); - - if (context != null) { - final baggage = context.toBaggage(log: _hub.options.log); - return SentryBaggageHeader.fromBaggage(baggage); - } - return null; - } - - @override - SentryTraceContextHeader? traceContext() { - // TODO: freeze context after 1st envelope or outgoing HTTP request - if (_sentryTraceContextHeader != null) { - return _sentryTraceContextHeader; - } - - _sentryTraceContextHeader = SentryTraceContextHeader( - _rootSpan.context.traceId, - _hub.options.parsedDsn.publicKey, - release: _hub.options.release, - environment: _hub.options.environment, - userId: null, // because of PII not sending it for now - transaction: - _isHighQualityTransactionName(transactionNameSource) ? name : null, - sampleRate: _sampleRateToString(_rootSpan.samplingDecision?.sampleRate), - sampleRand: _sampleRandToString(_rootSpan.samplingDecision?.sampleRand), - sampled: _rootSpan.samplingDecision?.sampled.toString(), - ); - - return _sentryTraceContextHeader; - } - - String? _sampleRateToString(double? sampleRate) { - if (!isValidSampleRate(sampleRate)) { - return null; - } - return sampleRate != null ? SampleRateFormat().format(sampleRate) : null; - } - - String? _sampleRandToString(double? sampleRand) { - if (!isValidSampleRand(sampleRand)) { - return null; - } - return sampleRand != null ? SampleRateFormat().format(sampleRand) : null; - } - - bool _isHighQualityTransactionName(SentryTransactionNameSource source) { - return source != SentryTransactionNameSource.url; - } - - @override - SentryTracesSamplingDecision? get samplingDecision => - _rootSpan.samplingDecision; - - @override - void scheduleFinish() { - if (finished) { - return; - } - if (_autoFinishAfterTimer != null) { - _scheduleTimer(); - } - } - - void _scheduleTimer() { - final autoFinishAfter = _autoFinishAfter; - if (autoFinishAfter != null) { - _autoFinishAfterTimer?.cancel(); - _autoFinishAfterTimer = Timer(autoFinishAfter, () async { - await finish(status: status ?? SpanStatus.ok()); - }); - } - } -} diff --git a/dart/lib/src/sentry_tracer_finish_status.dart b/dart/lib/src/sentry_tracer_finish_status.dart deleted file mode 100644 index eb0027d538..0000000000 --- a/dart/lib/src/sentry_tracer_finish_status.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../sentry.dart'; - -@internal -class SentryTracerFinishStatus { - final bool finishing; - final SpanStatus? status; - - SentryTracerFinishStatus.finishing(this.status) : finishing = true; - - SentryTracerFinishStatus.notFinishing() - : finishing = false, - status = null; -} diff --git a/dart/lib/src/sentry_traces_sampler.dart b/dart/lib/src/sentry_traces_sampler.dart deleted file mode 100644 index 3e1807b015..0000000000 --- a/dart/lib/src/sentry_traces_sampler.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'dart:math'; - -import 'package:meta/meta.dart'; - -import '../sentry.dart'; - -@internal -class SentryTracesSampler { - final SentryOptions _options; - final Random _random; - - SentryTracesSampler( - this._options, { - Random? random, - }) : _random = random ?? Random() { - if (_options.tracesSampler != null && _options.tracesSampleRate != null) { - _options.log(SentryLevel.warning, - 'Both tracesSampler and traceSampleRate are set. tracesSampler will take precedence and fallback to traceSampleRate if it returns null.'); - } - } - - SentryTracesSamplingDecision sample( - SentrySamplingContext samplingContext, - double sampleRand, - ) { - final samplingDecision = - samplingContext.transactionContext.samplingDecision; - if (samplingDecision != null) { - return samplingDecision; - } - - final tracesSampler = _options.tracesSampler; - if (tracesSampler != null) { - try { - final sampleRate = tracesSampler(samplingContext); - if (sampleRate != null) { - return _makeSampleDecision(sampleRate, sampleRand); - } - } catch (exception, stackTrace) { - _options.log( - SentryLevel.error, - 'The tracesSampler callback threw an exception', - exception: exception, - stackTrace: stackTrace, - ); - if (_options.automatedTestMode) { - rethrow; - } - } - } - - final parentSamplingDecision = - samplingContext.transactionContext.parentSamplingDecision; - if (parentSamplingDecision != null) { - return parentSamplingDecision; - } - - double? optionsRate = _options.tracesSampleRate; - if (optionsRate != null) { - return _makeSampleDecision(optionsRate, sampleRand); - } - - return SentryTracesSamplingDecision(false); - } - - bool sampleProfiling(SentryTracesSamplingDecision tracesSamplingDecision) { - double? optionsRate = _options.profilesSampleRate; - if (optionsRate == null || !tracesSamplingDecision.sampled) { - return false; - } - return _isSampled(optionsRate); - } - - SentryTracesSamplingDecision _makeSampleDecision( - double sampleRate, - double sampleRand, - ) { - final sampled = _isSampled(sampleRate, sampleRand: sampleRand); - return SentryTracesSamplingDecision(sampled, - sampleRate: sampleRate, sampleRand: sampleRand); - } - - bool _isSampled(double sampleRate, {double? sampleRand}) { - final rand = sampleRand ?? _random.nextDouble(); - return rand <= sampleRate; - } -} diff --git a/dart/lib/src/sentry_traces_sampling_decision.dart b/dart/lib/src/sentry_traces_sampling_decision.dart deleted file mode 100644 index 802d27f832..0000000000 --- a/dart/lib/src/sentry_traces_sampling_decision.dart +++ /dev/null @@ -1,11 +0,0 @@ -class SentryTracesSamplingDecision { - SentryTracesSamplingDecision( - this.sampled, { - this.sampleRate, - this.sampleRand, - }); - - final bool sampled; - final double? sampleRate; - final double? sampleRand; -} diff --git a/dart/lib/src/sentry_transaction_context.dart b/dart/lib/src/sentry_transaction_context.dart deleted file mode 100644 index 5002cb9b40..0000000000 --- a/dart/lib/src/sentry_transaction_context.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'protocol.dart'; -import 'sentry_baggage.dart'; -import 'sentry_trace_origins.dart'; -import 'tracing.dart'; - -class SentryTransactionContext extends SentrySpanContext { - String name; - SentryTransactionNameSource? transactionNameSource; - SentryTracesSamplingDecision? samplingDecision; - SentryTracesSamplingDecision? parentSamplingDecision; - - SentryTransactionContext( - this.name, - String operation, { - super.description, - super.traceId, - super.spanId, - super.parentSpanId, - this.transactionNameSource, - this.samplingDecision, - this.parentSamplingDecision, - super.origin, - }) : super( - operation: operation, - ); - - factory SentryTransactionContext.fromSentryTrace( - String name, - String operation, - SentryTraceHeader traceHeader, { - SentryTransactionNameSource? transactionNameSource, - SentryBaggage? baggage, - }) { - final sampleRate = baggage?.getSampleRate(); - final sampleRand = baggage?.getSampleRand(); - return SentryTransactionContext( - name, - operation, - traceId: traceHeader.traceId, - parentSpanId: traceHeader.spanId, - parentSamplingDecision: traceHeader.sampled != null - ? SentryTracesSamplingDecision( - traceHeader.sampled!, - sampleRate: sampleRate, - sampleRand: sampleRand, - ) - : null, - transactionNameSource: - transactionNameSource ?? SentryTransactionNameSource.custom, - origin: SentryTraceOrigins.manual, - ); - } - - @Deprecated('Assign values directly to the instance.') - SentryTransactionContext copyWith({ - String? name, - String? operation, - String? description, - SentryTracesSamplingDecision? parentSamplingDecision, - SentryId? traceId, - SpanId? spanId, - SpanId? parentSpanId, - SentryTransactionNameSource? transactionNameSource, - SentryTracesSamplingDecision? samplingDecision, - String? origin, - }) => - SentryTransactionContext( - name ?? this.name, - operation ?? this.operation, - description: description ?? this.description, - parentSamplingDecision: - parentSamplingDecision ?? this.parentSamplingDecision, - traceId: traceId ?? this.traceId, - spanId: spanId ?? this.spanId, - parentSpanId: parentSpanId ?? this.parentSpanId, - transactionNameSource: - transactionNameSource ?? this.transactionNameSource, - samplingDecision: samplingDecision ?? this.samplingDecision, - origin: origin ?? this.origin, - ); -} diff --git a/dart/lib/src/span_data_convention.dart b/dart/lib/src/span_data_convention.dart deleted file mode 100644 index 5979e6035a..0000000000 --- a/dart/lib/src/span_data_convention.dart +++ /dev/null @@ -1,10 +0,0 @@ -class SpanDataConvention { - SpanDataConvention._(); - - static const totalFrames = 'frames.total'; - static const slowFrames = 'frames.slow'; - static const frozenFrames = 'frames.frozen'; - static const framesDelay = 'frames.delay'; - - // TODO: eventually add other data keys here as well -} diff --git a/dart/lib/src/spotlight.dart b/dart/lib/src/spotlight.dart deleted file mode 100644 index e4c387a30f..0000000000 --- a/dart/lib/src/spotlight.dart +++ /dev/null @@ -1,12 +0,0 @@ -/// Spotlight configuration class. -class Spotlight { - /// Whether to enable Spotlight for local development. - bool enabled; - - /// The Spotlight Sidecar URL. - /// Defaults to http://10.0.2.2:8969/stream due to Emulator on Android. - /// Otherwise defaults to http://localhost:8969/stream. - String? url; - - Spotlight({required this.enabled, this.url}); -} diff --git a/dart/lib/src/throwable_mechanism.dart b/dart/lib/src/throwable_mechanism.dart deleted file mode 100644 index 8a3932a701..0000000000 --- a/dart/lib/src/throwable_mechanism.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'protocol/mechanism.dart'; - -/// A decorator that holds a Mechanism related to the decorated Exception -class ThrowableMechanism implements Exception { - final Mechanism _mechanism; - final dynamic _throwable; - final bool? _snapshot; - - ThrowableMechanism( - this._mechanism, - this._throwable, { - bool? snapshot, - }) : _snapshot = snapshot; - - Mechanism get mechanism => _mechanism; - - dynamic get throwable => _throwable; - - bool? get snapshot => _snapshot; -} diff --git a/dart/lib/src/tracing.dart b/dart/lib/src/tracing.dart deleted file mode 100644 index bc13c0a768..0000000000 --- a/dart/lib/src/tracing.dart +++ /dev/null @@ -1,10 +0,0 @@ -export 'sentry_transaction_context.dart'; -export 'sentry_sampling_context.dart'; -export 'sentry_span_context.dart'; -export 'sentry_span_interface.dart'; -export 'noop_sentry_span.dart'; -export 'invalid_sentry_trace_header_exception.dart'; -export 'sentry_measurement.dart'; -export 'sentry_measurement_unit.dart'; -export 'sentry_trace_context_header.dart'; -export 'sentry_traces_sampling_decision.dart'; diff --git a/dart/lib/src/transport/client_report_transport.dart b/dart/lib/src/transport/client_report_transport.dart deleted file mode 100644 index 13f518b020..0000000000 --- a/dart/lib/src/transport/client_report_transport.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:meta/meta.dart'; -import '../../sentry.dart'; -import '../sentry_envelope_header.dart'; -import 'rate_limiter.dart'; - -/// Decorator that handles attaching of client reports in tandem with rate -/// limiting. The rate limiter is optional. -@internal -class ClientReportTransport implements Transport { - final RateLimiter? _rateLimiter; - final SentryOptions _options; - final Transport _transport; - - ClientReportTransport(this._rateLimiter, this._options, this._transport); - - @visibleForTesting - RateLimiter? get rateLimiter => _rateLimiter; - - int _numberOfDroppedEnvelopes = 0; - - @visibleForTesting - int get numberOfDroppedEvents => _numberOfDroppedEnvelopes; - - @override - Future send(SentryEnvelope envelope) async { - final rateLimiter = _rateLimiter; - - SentryEnvelope? filteredEnvelope = envelope; - if (rateLimiter != null) { - filteredEnvelope = rateLimiter.filter(envelope); - } - if (filteredEnvelope == null) { - _numberOfDroppedEnvelopes += 1; - } - if (_numberOfDroppedEnvelopes >= 10) { - // Create new envelope that could only contain client reports - filteredEnvelope = SentryEnvelope( - SentryEnvelopeHeader(SentryId.newId(), _options.sdk), - [], - ); - } - if (filteredEnvelope == null) { - return SentryId.empty(); - } - _numberOfDroppedEnvelopes = 0; - - final clientReport = _options.recorder.flush(); - filteredEnvelope.addClientReport(clientReport); - - if (filteredEnvelope.items.isNotEmpty) { - return _transport.send(filteredEnvelope); - } else { - return SentryId.empty(); - } - } -} diff --git a/dart/lib/src/transport/data_category.dart b/dart/lib/src/transport/data_category.dart deleted file mode 100644 index 89f983f3a7..0000000000 --- a/dart/lib/src/transport/data_category.dart +++ /dev/null @@ -1,38 +0,0 @@ -/// Different category types of data sent to Sentry. Used for rate limiting and client reports. -enum DataCategory { - all, - dataCategoryDefault, // default - error, - session, - transaction, - span, - attachment, - security, - metricBucket, - logItem, - feedback, - unknown; - - static DataCategory fromItemType(String itemType) { - switch (itemType) { - case 'event': - return DataCategory.error; - case 'session': - return DataCategory.session; - case 'attachment': - return DataCategory.attachment; - case 'transaction': - return DataCategory.transaction; - // The envelope item type used for metrics is statsd, - // whereas the client report category is metric_bucket - case 'statsd': - return DataCategory.metricBucket; - case 'log': - return DataCategory.logItem; - case 'feedback': - return DataCategory.feedback; - default: - return DataCategory.unknown; - } - } -} diff --git a/dart/lib/src/transport/encode.dart b/dart/lib/src/transport/encode.dart deleted file mode 100644 index 645aaf02cd..0000000000 --- a/dart/lib/src/transport/encode.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:io'; - -/// Encodes the body using Gzip compression -List compressBody(List body, Map headers) { - headers['Content-Encoding'] = 'gzip'; - return gzip.encode(body); -} - -/// Encodes bytes in sink using Gzip compression -Sink> compressInSink( - Sink> sink, Map headers) { - headers['Content-Encoding'] = 'gzip'; - return GZipCodec().encoder.startChunkedConversion(sink); -} diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart deleted file mode 100644 index 73c8e41c69..0000000000 --- a/dart/lib/src/transport/http_transport.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:http/http.dart'; - -import '../http_client/client_provider.dart' - if (dart.library.io) '../http_client/io_client_provider.dart'; -import '../noop_client.dart'; -import '../protocol.dart'; -import '../sentry_envelope.dart'; -import '../sentry_options.dart'; -import '../utils/transport_utils.dart'; -import 'http_transport_request_handler.dart'; -import 'rate_limiter.dart'; -import 'transport.dart'; - -/// A transport is in charge of sending the event to the Sentry server. -class HttpTransport implements Transport { - final SentryOptions _options; - - final RateLimiter _rateLimiter; - - final HttpTransportRequestHandler _requestHandler; - - factory HttpTransport(SentryOptions options, RateLimiter rateLimiter) { - if (options.httpClient is NoOpClient) { - options.httpClient = getClientProvider().getClient(options); - } - return HttpTransport._(options, rateLimiter); - } - - HttpTransport._(this._options, this._rateLimiter) - : _requestHandler = - HttpTransportRequestHandler(_options, _options.parsedDsn.postUri); - - @override - Future send(SentryEnvelope envelope) async { - envelope.header.sentAt = _options.clock(); - - final streamedRequest = await _requestHandler.createRequest(envelope); - - final response = await _options.httpClient - .send(streamedRequest) - .then(Response.fromStream); - - _updateRetryAfterLimits(response); - - TransportUtils.logResponse(_options, envelope, response, target: 'Sentry'); - - if (response.statusCode == 200) { - return _parseEventId(response); - } - if (response.statusCode == 429) { - _options.log( - SentryLevel.warning, 'Rate limit reached, failed to send envelope'); - } - return SentryId.empty(); - } - - SentryId? _parseEventId(Response response) { - try { - final eventId = json.decode(response.body)['id']; - return eventId != null ? SentryId.fromId(eventId) : null; - } catch (e) { - _options.log(SentryLevel.error, 'Error parsing response: $e'); - if (_options.automatedTestMode) { - rethrow; - } - return null; - } - } - - void _updateRetryAfterLimits(Response response) { - // seconds - final retryAfterHeader = response.headers['Retry-After']; - - // X-Sentry-Rate-Limits looks like: seconds:categories:scope - // it could have more than one scope so it looks like: - // quota_limit, quota_limit, quota_limit - - // a real example: 50:transaction:key, 2700:default;error;security:organization - // 50::key is also a valid case, it means no categories and it should apply to all of them - final sentryRateLimitHeader = response.headers['X-Sentry-Rate-Limits']; - _rateLimiter.updateRetryAfterLimits( - sentryRateLimitHeader, retryAfterHeader, response.statusCode); - } -} diff --git a/dart/lib/src/transport/http_transport_request_handler.dart b/dart/lib/src/transport/http_transport_request_handler.dart deleted file mode 100644 index 9d1bdd44f4..0000000000 --- a/dart/lib/src/transport/http_transport_request_handler.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'dart:async'; - -import 'package:http/http.dart'; -import 'package:meta/meta.dart'; - -import 'noop_encode.dart' if (dart.library.io) 'encode.dart'; -import '../protocol.dart'; -import '../sentry_options.dart'; -import '../sentry_envelope.dart'; - -@internal -class HttpTransportRequestHandler { - final SentryOptions _options; - final Dsn _dsn; - final Map _headers; - final Uri _requestUri; - late _CredentialBuilder _credentialBuilder; - - HttpTransportRequestHandler(this._options, this._requestUri) - : _dsn = _options.parsedDsn, - _headers = _buildHeaders( - _options.platform.isWeb, - _options.sentryClientName, - ) { - _credentialBuilder = _CredentialBuilder( - _dsn, - _options.sentryClientName, - ); - } - - Future createRequest(SentryEnvelope envelope) async { - final streamedRequest = StreamedRequest('POST', _requestUri); - - if (_options.compressPayload) { - final compressionSink = compressInSink(streamedRequest.sink, _headers); - envelope - .envelopeStream(_options) - .listen(compressionSink.add) - .onDone(compressionSink.close); - } else { - envelope - .envelopeStream(_options) - .listen(streamedRequest.sink.add) - .onDone(streamedRequest.sink.close); - } - - streamedRequest.headers.addAll(_credentialBuilder.configure(_headers)); - return streamedRequest; - } -} - -Map _buildHeaders(bool isWeb, String sdkIdentifier) { - final headers = {'Content-Type': 'application/x-sentry-envelope'}; - // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why - // for web it use browser user agent - if (!isWeb) { - headers['User-Agent'] = sdkIdentifier; - } - return headers; -} - -class _CredentialBuilder { - final String _authHeader; - - _CredentialBuilder._(String authHeader) : _authHeader = authHeader; - - factory _CredentialBuilder(Dsn dsn, String sdkIdentifier) { - final authHeader = _buildAuthHeader( - publicKey: dsn.publicKey, - secretKey: dsn.secretKey, - sdkIdentifier: sdkIdentifier, - ); - - return _CredentialBuilder._(authHeader); - } - - static String _buildAuthHeader({ - required String publicKey, - String? secretKey, - required String sdkIdentifier, - }) { - var header = 'Sentry sentry_version=7, sentry_client=$sdkIdentifier, ' - 'sentry_key=$publicKey'; - - if (secretKey != null) { - header += ', sentry_secret=$secretKey'; - } - - return header; - } - - Map configure(Map headers) { - return headers - ..addAll( - {'X-Sentry-Auth': _authHeader}, - ); - } -} diff --git a/dart/lib/src/transport/noop_encode.dart b/dart/lib/src/transport/noop_encode.dart deleted file mode 100644 index fa1f9c5547..0000000000 --- a/dart/lib/src/transport/noop_encode.dart +++ /dev/null @@ -1,7 +0,0 @@ -/// gzip compression is not available on browser -List compressBody(List body, Map headers) => body; - -/// gzip compression is not available on browser -Sink> compressInSink( - Sink> sink, Map headers) => - sink; diff --git a/dart/lib/src/transport/noop_transport.dart b/dart/lib/src/transport/noop_transport.dart deleted file mode 100644 index f4ae138e99..0000000000 --- a/dart/lib/src/transport/noop_transport.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'dart:async'; - -import '../sentry_envelope.dart'; - -import '../protocol.dart'; -import 'transport.dart'; - -class NoOpTransport implements Transport { - @override - Future send(SentryEnvelope envelope) async => null; -} diff --git a/dart/lib/src/transport/rate_limit.dart b/dart/lib/src/transport/rate_limit.dart deleted file mode 100644 index 00284a3ba7..0000000000 --- a/dart/lib/src/transport/rate_limit.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'data_category.dart'; - -/// `RateLimit` containing limited `DataCategory` and duration in milliseconds. -class RateLimit { - RateLimit(this.category, this.duration, {List? namespaces}) - : namespaces = (namespaces?..removeWhere((e) => e.isEmpty)) ?? []; - - final DataCategory category; - final Duration duration; - final List namespaces; -} diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart deleted file mode 100644 index f0fea1dfde..0000000000 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'data_category.dart'; -import 'rate_limit.dart'; - -/// Parse rate limit categories and times from response header payloads. -class RateLimitParser { - RateLimitParser(this._header); - - static const httpRetryAfterDefaultDelay = Duration(milliseconds: 60000); - - final String? _header; - - List parseRateLimitHeader() { - final rateLimitHeader = _header; - if (rateLimitHeader == null) { - return []; - } - // example: 2700:metric_bucket:organization:quota_exceeded:custom,... - final rateLimits = []; - final rateLimitValues = rateLimitHeader.toLowerCase().split(','); - for (final rateLimitValue in rateLimitValues) { - final durationAndCategories = rateLimitValue.trim().split(':'); - if (durationAndCategories.isEmpty) { - continue; - } - final duration = _parseRetryAfterOrDefault(durationAndCategories[0]); - if (durationAndCategories.length <= 1) { - continue; - } - final allCategories = durationAndCategories[1]; - if (allCategories.isNotEmpty) { - final categoryValues = allCategories.split(';'); - for (final categoryValue in categoryValues) { - final category = _DataCategoryExtension._fromStringValue( - categoryValue); // Metric buckets rate limit can have namespaces - if (category == DataCategory.metricBucket) { - final namespaces = durationAndCategories.length > 4 - ? durationAndCategories[4] - : null; - rateLimits.add(RateLimit( - category, - duration, - namespaces: namespaces?.trim().split(','), - )); - } else if (category != DataCategory.unknown) { - rateLimits.add(RateLimit(category, duration)); - } - } - } else { - rateLimits.add(RateLimit(DataCategory.all, duration)); - } - } - return rateLimits; - } - - List parseRetryAfterHeader() { - return [RateLimit(DataCategory.all, _parseRetryAfterOrDefault(_header))]; - } - - // Helper - - static Duration _parseRetryAfterOrDefault(String? value) { - final durationInSeconds = int.tryParse(value ?? ''); - if (durationInSeconds != null) { - return Duration(seconds: durationInSeconds); - } else { - return RateLimitParser.httpRetryAfterDefaultDelay; - } - } -} - -extension _DataCategoryExtension on DataCategory { - static DataCategory _fromStringValue(String stringValue) { - switch (stringValue) { - case '__all__': - return DataCategory.all; - case 'default': - return DataCategory.dataCategoryDefault; - case 'error': - return DataCategory.error; - case 'session': - return DataCategory.session; - case 'transaction': - return DataCategory.transaction; - case 'attachment': - return DataCategory.attachment; - case 'security': - return DataCategory.security; - case 'metric_bucket': - return DataCategory.metricBucket; - case 'log_item': - return DataCategory.logItem; - } - return DataCategory.unknown; - } -} diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart deleted file mode 100644 index fa0f7a9018..0000000000 --- a/dart/lib/src/transport/rate_limiter.dart +++ /dev/null @@ -1,124 +0,0 @@ -import '../../sentry.dart'; -import '../transport/rate_limit_parser.dart'; -import 'rate_limit.dart'; -import 'data_category.dart'; -import '../client_reports/discard_reason.dart'; - -/// Controls retry limits on different category types sent to Sentry. -class RateLimiter { - RateLimiter(this._options); - - final SentryOptions _options; - final _rateLimitedUntil = {}; - - /// Filter out envelopes that are rate limited. - SentryEnvelope? filter(SentryEnvelope envelope) { - // Optimize for/No allocations if no items are under 429 - List? dropItems; - for (final item in envelope.items) { - // using the raw value of the enum to not expose SentryEnvelopeItemType - if (_isRetryAfter(item.header.type)) { - dropItems ??= []; - dropItems.add(item); - - _options.recorder.recordLostEvent( - DiscardReason.rateLimitBackoff, - DataCategory.fromItemType(item.header.type), - ); - - final originalObject = item.originalObject; - if (originalObject is SentryTransaction) { - _options.recorder.recordLostEvent( - DiscardReason.rateLimitBackoff, - DataCategory.span, - count: originalObject.spans.length + 1, - ); - } - } - } - - if (dropItems != null) { - // Need a new envelope - final toSend = []; - for (final item in envelope.items) { - if (!dropItems.contains(item)) { - toSend.add(item); - } - } - - // no reason to continue - if (toSend.isEmpty) { - return null; - } - - return SentryEnvelope(envelope.header, toSend); - } else { - return envelope; - } - } - - /// Update rate limited categories - void updateRetryAfterLimits( - String? sentryRateLimitHeader, String? retryAfterHeader, int errorCode) { - final currentDateTime = _options.clock().millisecondsSinceEpoch; - var rateLimits = []; - - if (sentryRateLimitHeader != null) { - rateLimits = - RateLimitParser(sentryRateLimitHeader).parseRateLimitHeader(); - } else if (errorCode == 429) { - rateLimits = RateLimitParser(retryAfterHeader).parseRetryAfterHeader(); - } - - for (final rateLimit in rateLimits) { - if (rateLimit.category == DataCategory.metricBucket && - rateLimit.namespaces.isNotEmpty && - !rateLimit.namespaces.contains('custom')) { - continue; - } - _applyRetryAfterOnlyIfLonger( - rateLimit.category, - DateTime.fromMillisecondsSinceEpoch( - currentDateTime + rateLimit.duration.inMilliseconds), - ); - } - } - - // Private - - bool _isRetryAfter(String itemType) { - final dataCategory = DataCategory.fromItemType(itemType); - final currentDate = DateTime.fromMillisecondsSinceEpoch( - _options.clock().millisecondsSinceEpoch); - - // check all categories - final dateAllCategories = _rateLimitedUntil[DataCategory.all]; - if (dateAllCategories != null) { - if (!currentDate.isAfter(dateAllCategories)) { - return true; - } - } - - // Unknown should not be rate limited - if (DataCategory.unknown == dataCategory) { - return false; - } - - // check for specific dataCategory - final dateCategory = _rateLimitedUntil[dataCategory]; - if (dateCategory != null) { - return !currentDate.isAfter(dateCategory); - } - - return false; - } - - void _applyRetryAfterOnlyIfLonger(DataCategory dataCategory, DateTime date) { - final oldDate = _rateLimitedUntil[dataCategory]; - - // only overwrite its previous date if the limit is even longer - if (oldDate == null || date.isAfter(oldDate)) { - _rateLimitedUntil[dataCategory] = date; - } - } -} diff --git a/dart/lib/src/transport/spotlight_http_transport.dart b/dart/lib/src/transport/spotlight_http_transport.dart deleted file mode 100644 index e2c98fda2a..0000000000 --- a/dart/lib/src/transport/spotlight_http_transport.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:http/http.dart'; -import '../utils/transport_utils.dart'; -import 'http_transport_request_handler.dart'; - -import '../../sentry.dart'; -import '../noop_client.dart'; -import '../http_client/client_provider.dart' - if (dart.library.io) '../http_client/io_client_provider.dart'; - -/// Spotlight HTTP transport decorator that sends Sentry envelopes to both Sentry and Spotlight. -/// This will be used on platforms that do not have native SDK support. -/// Platforms with native SDK support will configure spotlight directly in the native SDK options. -class SpotlightHttpTransport extends Transport { - final SentryOptions _options; - final Transport _transport; - final HttpTransportRequestHandler _requestHandler; - - factory SpotlightHttpTransport(SentryOptions options, Transport transport) { - if (options.httpClient is NoOpClient) { - options.httpClient = getClientProvider().getClient(options); - } - return SpotlightHttpTransport._(options, transport); - } - - SpotlightHttpTransport._(this._options, this._transport) - : _requestHandler = HttpTransportRequestHandler(_options, - Uri.parse(_options.spotlight.url ?? _defaultSpotlightUrl())); - - @override - Future send(SentryEnvelope envelope) async { - try { - await _sendToSpotlight(envelope); - } catch (e) { - _options.log( - SentryLevel.warning, 'Failed to send envelope to Spotlight: $e'); - if (_options.automatedTestMode) { - rethrow; - } - } - return _transport.send(envelope); - } - - Future _sendToSpotlight(SentryEnvelope envelope) async { - envelope.header.sentAt = _options.clock(); - - final spotlightRequest = await _requestHandler.createRequest(envelope); - - final response = await _options.httpClient - .send(spotlightRequest) - .then(Response.fromStream); - - TransportUtils.logResponse(_options, envelope, response, - target: 'Spotlight'); - } -} - -String _defaultSpotlightUrl() { - return 'http://localhost:8969/stream'; -} diff --git a/dart/lib/src/transport/task_queue.dart b/dart/lib/src/transport/task_queue.dart deleted file mode 100644 index 4c99393caa..0000000000 --- a/dart/lib/src/transport/task_queue.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'dart:async'; - -import 'package:meta/meta.dart'; - -import '../../sentry.dart'; -import '../client_reports/client_report_recorder.dart'; -import '../client_reports/discard_reason.dart'; -import 'data_category.dart'; - -typedef Task = Future Function(); - -@internal -abstract class TaskQueue { - Future enqueue(Task task, T fallbackResult, DataCategory category); -} - -@internal -class DefaultTaskQueue implements TaskQueue { - DefaultTaskQueue(this._maxQueueSize, this._logger, this._recorder); - - final int _maxQueueSize; - final SdkLogCallback _logger; - final ClientReportRecorder _recorder; - - int _queueCount = 0; - - @override - Future enqueue( - Task task, - T fallbackResult, - DataCategory category, - ) async { - if (_queueCount >= _maxQueueSize) { - _recorder.recordLostEvent(DiscardReason.queueOverflow, category); - _logger( - SentryLevel.warning, - 'Task dropped due to reaching max ($_maxQueueSize} parallel tasks.).', - ); - return fallbackResult; - } else { - _queueCount++; - try { - return await task(); - } finally { - _queueCount--; - } - } - } -} - -@internal -class NoOpTaskQueue implements TaskQueue { - @override - Future enqueue( - Task task, - T fallbackResult, - DataCategory category, - ) { - return task(); - } -} diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart deleted file mode 100644 index f0a6a2c996..0000000000 --- a/dart/lib/src/transport/transport.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'dart:async'; - -import '../sentry_envelope.dart'; -import '../protocol.dart'; - -/// A transport is in charge of sending the event/envelope either via http -/// or caching in the disk. -abstract class Transport { - Future send(SentryEnvelope envelope); -} diff --git a/dart/lib/src/type_check_hint.dart b/dart/lib/src/type_check_hint.dart deleted file mode 100644 index 7e47c1ecbf..0000000000 --- a/dart/lib/src/type_check_hint.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:meta/meta.dart'; -import 'http_client/failed_request_client.dart'; - -/// Constants used for Type Check hints. -class TypeCheckHint { - /// Used for Synthetic exceptions. - static const syntheticException = 'syntheticException'; - - /// Used for [FailedRequestClient] for request hint - static const httpRequest = 'request'; - - /// Used for [FailedRequestClient] for response hint - static const httpResponse = 'response'; - - /// Used for `sentry_logging/LoggingIntegration` for `sentry_logging/LogRecord` hint - static const record = 'record'; - - /// Widget that was tapped in `sentry_flutter/SentryUserInteractionWidget` - static const widget = 'widget'; - - /// Used to indicate that the SDK added a synthetic current stack trace. - static const currentStackTrace = 'currentStackTrace'; - - @internal - static const isWidgetFeedback = 'isWidgetFeedback'; -} diff --git a/dart/lib/src/utils.dart b/dart/lib/src/utils.dart deleted file mode 100644 index db5ca614c2..0000000000 --- a/dart/lib/src/utils.dart +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:convert'; - -import 'package:meta/meta.dart'; - -/// Sentry does not take a timezone and instead expects the date-time to be -/// submitted in UTC timezone. -@internal -DateTime getUtcDateTime() => DateTime.now().toUtc(); - -/// Formats a Date as ISO8601 and UTC with millis precision -@internal -String formatDateAsIso8601WithMillisPrecision(DateTime date) { - var iso = date.toIso8601String(); - final millisecondSeparatorIndex = iso.lastIndexOf('.'); - if (millisecondSeparatorIndex != -1) { - // + 4 for millis precision - iso = iso.substring(0, millisecondSeparatorIndex + 4); - } - // appends Z because the substring removed it - return '${iso}Z'; -} - -@internal -final utf8JsonEncoder = JsonUtf8Encoder(null, jsonSerializationFallback, null); - -@internal -Object? jsonSerializationFallback(Object? nonEncodable) { - if (nonEncodable == null) { - return null; - } - return nonEncodable.toString(); -} diff --git a/dart/lib/src/utils/_io_get_isolate_name.dart b/dart/lib/src/utils/_io_get_isolate_name.dart deleted file mode 100644 index 93a7c993d0..0000000000 --- a/dart/lib/src/utils/_io_get_isolate_name.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'dart:isolate'; - -String? getIsolateName() => Isolate.current.debugName; diff --git a/dart/lib/src/utils/_io_get_sentry_operating_system.dart b/dart/lib/src/utils/_io_get_sentry_operating_system.dart deleted file mode 100644 index e825ccfd6e..0000000000 --- a/dart/lib/src/utils/_io_get_sentry_operating_system.dart +++ /dev/null @@ -1,72 +0,0 @@ -import '../protocol/sentry_operating_system.dart'; -import 'dart:io'; -import 'package:meta/meta.dart'; - -@internal -SentryOperatingSystem getSentryOperatingSystem({ - String? name, - String? rawDescription, -}) { - name ??= Platform.operatingSystem; - rawDescription ??= Platform.operatingSystemVersion; - RegExpMatch? match; - switch (name) { - case 'android': - match = _androidOsRegexp.firstMatch(rawDescription); - name = 'Android'; - break; - case 'ios': - name = 'iOS'; - match = _appleOsRegexp.firstMatch(rawDescription); - break; - case 'macos': - name = 'macOS'; - match = _appleOsRegexp.firstMatch(rawDescription); - break; - case 'linux': - name = 'Linux'; - match = _linuxOsRegexp.firstMatch(rawDescription); - break; - case 'windows': - name = 'Windows'; - match = _windowsOsRegexp.firstMatch(rawDescription); - break; - } - - return SentryOperatingSystem( - name: name, - rawDescription: rawDescription, - version: match?.namedGroupOrNull('version'), - build: match?.namedGroupOrNull('build'), - kernelVersion: match?.namedGroupOrNull('kernelVersion'), - ); -} - -// LYA-L29 10.1.0.289(C432E7R1P5) -// TE1A.220922.010 -final _androidOsRegexp = RegExp('^(?.*)\$', caseSensitive: false); - -// Linux 5.11.0-1018-gcp #20~20.04.2-Ubuntu SMP Fri Sep 3 01:01:37 UTC 2021 -final _linuxOsRegexp = RegExp( - '(?[a-z0-9+.\\-]+) (?#.*)\$', - caseSensitive: false); - -// Version 14.5 (Build 18E182) -final _appleOsRegexp = RegExp( - '(?[a-z0-9+.\\-]+)( \\(Build (?[a-z0-9+.\\-]+))\\)?\$', - caseSensitive: false); - -// "Windows 10 Pro" 10.0 (Build 19043) -final _windowsOsRegexp = RegExp( - ' (?[a-z0-9+.\\-]+)( \\(Build (?[a-z0-9+.\\-]+))\\)?\$', - caseSensitive: false); - -extension on RegExpMatch { - String? namedGroupOrNull(String name) { - if (groupNames.contains(name)) { - return namedGroup(name); - } else { - return null; - } - } -} diff --git a/dart/lib/src/utils/_web_get_isolate_name.dart b/dart/lib/src/utils/_web_get_isolate_name.dart deleted file mode 100644 index 0db3f82b99..0000000000 --- a/dart/lib/src/utils/_web_get_isolate_name.dart +++ /dev/null @@ -1 +0,0 @@ -String? getIsolateName() => null; diff --git a/dart/lib/src/utils/_web_get_sentry_operating_system.dart b/dart/lib/src/utils/_web_get_sentry_operating_system.dart deleted file mode 100644 index 1ec1309398..0000000000 --- a/dart/lib/src/utils/_web_get_sentry_operating_system.dart +++ /dev/null @@ -1,13 +0,0 @@ -import '../protocol/sentry_operating_system.dart'; -import 'package:meta/meta.dart'; - -@internal -SentryOperatingSystem getSentryOperatingSystem({ - String? name, - String? rawDescription, -}) { - final os = SentryOperatingSystem(); - os.name = name ?? os.name; - os.rawDescription = rawDescription ?? os.rawDescription; - return os; -} diff --git a/dart/lib/src/utils/breadcrumb_log_level.dart b/dart/lib/src/utils/breadcrumb_log_level.dart deleted file mode 100644 index 71eb86ec47..0000000000 --- a/dart/lib/src/utils/breadcrumb_log_level.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../../sentry.dart'; - -/// Determine a breadcrumb's log level (only `warning` or `error`) based on an HTTP status code. -@internal -SentryLevel? getBreadcrumbLogLevelFromHttpStatusCode(int statusCode) { - // NOTE: null defaults to 'info' in Sentry - if (statusCode >= 400 && statusCode < 500) { - return SentryLevel.warning; - } else if (statusCode >= 500 && statusCode < 600) { - return SentryLevel.error; - } else { - return null; - } -} diff --git a/dart/lib/src/utils/crc32_utils.dart b/dart/lib/src/utils/crc32_utils.dart deleted file mode 100644 index 8e6f63fd53..0000000000 --- a/dart/lib/src/utils/crc32_utils.dart +++ /dev/null @@ -1,313 +0,0 @@ -// Adapted from the archive library (https://pub.dev/packages/archive) -// https://github.com/brendan-duncan/archive/blob/21c864efe0df2b7fd962b59ff0a714c96732bf7d/lib/src/util/crc32.dart -// -// The MIT License -// -// Copyright (c) 2013-2021 Brendan Duncan. -// All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -/// Util class to compute the CRC-32 checksum of a given array. -class Crc32Utils { - /// Get the CRC-32 checksum of the given array. You can append bytes to an - /// already computed crc by specifying the previous [crc] value. - static int getCrc32(List array, [int crc = 0]) { - var len = array.length; - crc = crc ^ 0xffffffff; - var ip = 0; - while (len >= 8) { - crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); - crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); - crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); - crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); - crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); - crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); - crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); - crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); - len -= 8; - } - if (len > 0) { - do { - crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); - } while (--len > 0); - } - return crc ^ 0xffffffff; - } -} - -// Precomputed CRC table for faster calculations. -const List _crc32Table = [ - 0, - 1996959894, - 3993919788, - 2567524794, - 124634137, - 1886057615, - 3915621685, - 2657392035, - 249268274, - 2044508324, - 3772115230, - 2547177864, - 162941995, - 2125561021, - 3887607047, - 2428444049, - 498536548, - 1789927666, - 4089016648, - 2227061214, - 450548861, - 1843258603, - 4107580753, - 2211677639, - 325883990, - 1684777152, - 4251122042, - 2321926636, - 335633487, - 1661365465, - 4195302755, - 2366115317, - 997073096, - 1281953886, - 3579855332, - 2724688242, - 1006888145, - 1258607687, - 3524101629, - 2768942443, - 901097722, - 1119000684, - 3686517206, - 2898065728, - 853044451, - 1172266101, - 3705015759, - 2882616665, - 651767980, - 1373503546, - 3369554304, - 3218104598, - 565507253, - 1454621731, - 3485111705, - 3099436303, - 671266974, - 1594198024, - 3322730930, - 2970347812, - 795835527, - 1483230225, - 3244367275, - 3060149565, - 1994146192, - 31158534, - 2563907772, - 4023717930, - 1907459465, - 112637215, - 2680153253, - 3904427059, - 2013776290, - 251722036, - 2517215374, - 3775830040, - 2137656763, - 141376813, - 2439277719, - 3865271297, - 1802195444, - 476864866, - 2238001368, - 4066508878, - 1812370925, - 453092731, - 2181625025, - 4111451223, - 1706088902, - 314042704, - 2344532202, - 4240017532, - 1658658271, - 366619977, - 2362670323, - 4224994405, - 1303535960, - 984961486, - 2747007092, - 3569037538, - 1256170817, - 1037604311, - 2765210733, - 3554079995, - 1131014506, - 879679996, - 2909243462, - 3663771856, - 1141124467, - 855842277, - 2852801631, - 3708648649, - 1342533948, - 654459306, - 3188396048, - 3373015174, - 1466479909, - 544179635, - 3110523913, - 3462522015, - 1591671054, - 702138776, - 2966460450, - 3352799412, - 1504918807, - 783551873, - 3082640443, - 3233442989, - 3988292384, - 2596254646, - 62317068, - 1957810842, - 3939845945, - 2647816111, - 81470997, - 1943803523, - 3814918930, - 2489596804, - 225274430, - 2053790376, - 3826175755, - 2466906013, - 167816743, - 2097651377, - 4027552580, - 2265490386, - 503444072, - 1762050814, - 4150417245, - 2154129355, - 426522225, - 1852507879, - 4275313526, - 2312317920, - 282753626, - 1742555852, - 4189708143, - 2394877945, - 397917763, - 1622183637, - 3604390888, - 2714866558, - 953729732, - 1340076626, - 3518719985, - 2797360999, - 1068828381, - 1219638859, - 3624741850, - 2936675148, - 906185462, - 1090812512, - 3747672003, - 2825379669, - 829329135, - 1181335161, - 3412177804, - 3160834842, - 628085408, - 1382605366, - 3423369109, - 3138078467, - 570562233, - 1426400815, - 3317316542, - 2998733608, - 733239954, - 1555261956, - 3268935591, - 3050360625, - 752459403, - 1541320221, - 2607071920, - 3965973030, - 1969922972, - 40735498, - 2617837225, - 3943577151, - 1913087877, - 83908371, - 2512341634, - 3803740692, - 2075208622, - 213261112, - 2463272603, - 3855990285, - 2094854071, - 198958881, - 2262029012, - 4057260610, - 1759359992, - 534414190, - 2176718541, - 4139329115, - 1873836001, - 414664567, - 2282248934, - 4279200368, - 1711684554, - 285281116, - 2405801727, - 4167216745, - 1634467795, - 376229701, - 2685067896, - 3608007406, - 1308918612, - 956543938, - 2808555105, - 3495958263, - 1231636301, - 1047427035, - 2932959818, - 3654703836, - 1088359270, - 936918000, - 2847714899, - 3736837829, - 1202900863, - 817233897, - 3183342108, - 3401237130, - 1404277552, - 615818150, - 3134207493, - 3453421203, - 1423857449, - 601450431, - 3009837614, - 3294710456, - 1567103746, - 711928724, - 3020668471, - 3272380065, - 1510334235, - 755167117 -]; diff --git a/dart/lib/src/utils/http_header_utils.dart b/dart/lib/src/utils/http_header_utils.dart deleted file mode 100644 index a055f47a78..0000000000 --- a/dart/lib/src/utils/http_header_utils.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Helper to extract header data -@internal -class HttpHeaderUtils { - /// Get `Content-Length` header - static int? getContentLength(Map> headers) { - final contentLengthHeader = - headers['content-length'] ?? headers['Content-Length']; - if (contentLengthHeader != null && contentLengthHeader.isNotEmpty) { - final headerValue = contentLengthHeader.first; - return int.tryParse(headerValue); - } - return null; - } -} diff --git a/dart/lib/src/utils/http_sanitizer.dart b/dart/lib/src/utils/http_sanitizer.dart deleted file mode 100644 index 8d0fbf6215..0000000000 --- a/dart/lib/src/utils/http_sanitizer.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:meta/meta.dart'; - -import '../protocol.dart'; -import 'url_details.dart'; - -@internal -class HttpSanitizer { - static final List _securityHeaders = [ - "X-FORWARDED-FOR", - "AUTHORIZATION", - "COOKIE", - "SET-COOKIE", - "X-API-KEY", - "X-REAL-IP", - "REMOTE-ADDR", - "FORWARDED", - "PROXY-AUTHORIZATION", - "X-CSRF-TOKEN", - "X-CSRFTOKEN", - "X-XSRF-TOKEN" - ]; - - /// Parse and sanitize url data for sentry.io - static UrlDetails? sanitizeUrl(String? url) { - if (url == null) { - return null; - } - - final queryIndex = url.indexOf('?'); - final fragmentIndex = url.indexOf('#'); - - if (queryIndex > -1 && fragmentIndex > -1 && fragmentIndex < queryIndex) { - // url considered malformed because of fragment position - return UrlDetails(); - } else { - try { - final uri = Uri.parse(url); - final urlWithRedactedAuth = uri._urlWithRedactedAuth(); - return UrlDetails( - url: urlWithRedactedAuth.isEmpty ? null : urlWithRedactedAuth, - query: uri.query.isEmpty ? null : uri.query, - fragment: uri.fragment.isEmpty ? null : uri.fragment); - } catch (_) { - return null; - } - } - } - - static Map? sanitizedHeaders(Map? headers) { - if (headers == null) { - return null; - } - final sanitizedHeaders = {}; - headers.forEach((key, value) { - if (!_securityHeaders.contains(key.toUpperCase())) { - sanitizedHeaders[key] = value; - } - }); - return sanitizedHeaders; - } -} - -extension _UriPath on Uri { - String _urlWithRedactedAuth() { - var buffer = ''; - if (scheme.isNotEmpty) { - buffer += '$scheme://'; - } - if (userInfo.isNotEmpty) { - buffer += - userInfo.contains(":") ? "[Filtered]:[Filtered]@" : "[Filtered]@"; - } - buffer += host; - if (path.isNotEmpty) { - buffer += path; - } - return buffer; - } -} - -@internal -extension SanitizedSentryRequest on SentryRequest { - void sanitize() { - final urlDetails = HttpSanitizer.sanitizeUrl(url) ?? UrlDetails(); - url = urlDetails.urlOrFallback; - queryString = urlDetails.query; - fragment = urlDetails.fragment; - headers = HttpSanitizer.sanitizedHeaders(headers) ?? {}; - cookies = null; - } -} diff --git a/dart/lib/src/utils/isolate_utils.dart b/dart/lib/src/utils/isolate_utils.dart deleted file mode 100644 index 0db3332677..0000000000 --- a/dart/lib/src/utils/isolate_utils.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:meta/meta.dart'; - -import '_io_get_isolate_name.dart' - if (dart.library.js_interop) '_web_get_isolate_name.dart' as isolate_getter; - -@internal -String? getIsolateName() => isolate_getter.getIsolateName(); diff --git a/dart/lib/src/utils/iterable_utils.dart b/dart/lib/src/utils/iterable_utils.dart deleted file mode 100644 index c2fb5e5c69..0000000000 --- a/dart/lib/src/utils/iterable_utils.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:meta/meta.dart'; - -@internal -class IterableUtils { - static T? firstWhereOrNull( - Iterable? iterable, - bool Function(T item) test, - ) { - if (iterable == null) { - return null; - } - for (var item in iterable) { - if (test(item)) return item; - } - return null; - } -} diff --git a/dart/lib/src/utils/os_utils.dart b/dart/lib/src/utils/os_utils.dart deleted file mode 100644 index a1b0cdf8c2..0000000000 --- a/dart/lib/src/utils/os_utils.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:meta/meta.dart'; -import '../protocol/sentry_operating_system.dart'; - -import '_web_get_sentry_operating_system.dart' - if (dart.library.io) '_io_get_sentry_operating_system.dart' as os_getter; - -@internal -SentryOperatingSystem getSentryOperatingSystem({ - String? name, - String? rawDescription, -}) { - return os_getter.getSentryOperatingSystem( - name: name, rawDescription: rawDescription); -} diff --git a/dart/lib/src/utils/regex_utils.dart b/dart/lib/src/utils/regex_utils.dart deleted file mode 100644 index ba64f7504e..0000000000 --- a/dart/lib/src/utils/regex_utils.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:meta/meta.dart'; - -@internal -bool isMatchingRegexPattern(String value, List regexPattern, - {bool caseSensitive = false}) { - final combinedRegexPattern = regexPattern.join('|'); - final regExp = RegExp(combinedRegexPattern, caseSensitive: caseSensitive); - return regExp.hasMatch(value); -} diff --git a/dart/lib/src/utils/sample_rate_format.dart b/dart/lib/src/utils/sample_rate_format.dart deleted file mode 100644 index 6fb0bcb7e4..0000000000 --- a/dart/lib/src/utils/sample_rate_format.dart +++ /dev/null @@ -1,324 +0,0 @@ -// ignore: dangling_library_doc_comments -/// Code ported & adapted from `intl` package -/// https://pub.dev/packages/intl -/// -/// License: -/// -/// Copyright 2013, the Dart project authors. -/// -/// Redistribution and use in source and binary forms, with or without -/// modification, are permitted provided that the following conditions are -/// met: -/// -/// * Redistributions of source code must retain the above copyright -/// notice, this list of conditions and the following disclaimer. -/// * Redistributions in binary form must reproduce the above -/// copyright notice, this list of conditions and the following -/// disclaimer in the documentation and/or other materials provided -/// with the distribution. -/// * Neither the name of Google LLC nor the names of its -/// contributors may be used to endorse or promote products derived -/// from this software without specific prior written permission. -/// -/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -/// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -/// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -/// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -/// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -/// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -/// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -/// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -/// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -/// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -/// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import 'dart:math'; - -import 'package:meta/meta.dart'; - -@internal -class SampleRateFormat { - int _minimumIntegerDigits; - int _maximumFractionDigits; - int _minimumFractionDigits; - - /// The difference between our zero and '0'. - /// - /// In other words, a constant _localeZero - _zero. Initialized when - /// the locale is set. - final int _zeroOffset; - - /// Caches the symbols - final _NumberSymbols _symbols; - - /// Transient internal state in which to build up the result of the format - /// operation. We can have this be just an instance variable because Dart is - /// single-threaded and unless we do an asynchronous operation in the process - /// of formatting then there will only ever be one number being formatted - /// at a time. In languages with threads we'd need to pass this on the stack. - final StringBuffer _buffer = StringBuffer(); - - factory SampleRateFormat() { - var symbols = _NumberSymbols( - DECIMAL_SEP: '.', - ZERO_DIGIT: '0', - ); - var localeZero = symbols.ZERO_DIGIT.codeUnitAt(0); - var zeroOffset = localeZero - '0'.codeUnitAt(0); - - return SampleRateFormat._( - symbols, - zeroOffset, - ); - } - - SampleRateFormat._(this._symbols, this._zeroOffset) - : _minimumIntegerDigits = 1, - _maximumFractionDigits = 16, - _minimumFractionDigits = 0; - - /// Format the sample rate - String format(dynamic sampleRate) { - try { - if (_isNaN(sampleRate)) return '0'; - if (_isSmallerZero(sampleRate)) { - sampleRate = 0; - } - if (_isLargerOne(sampleRate)) { - sampleRate = 1; - } - _formatFixed(sampleRate.abs()); - - var result = _buffer.toString(); - _buffer.clear(); - return result; - } catch (_) { - _buffer.clear(); - return '0'; - } - } - - /// Used to test if we have exceeded integer limits. - static final _maxInt = 1 is double ? pow(2, 52) : 1.0e300.floor(); - static final _maxDigits = (log(_maxInt) / log(10)).ceil(); - - bool _isNaN(dynamic number) => number is num ? number.isNaN : false; - bool _isSmallerZero(dynamic number) => number is num ? number < 0 : false; - bool _isLargerOne(dynamic number) => number is num ? number > 1 : false; - - /// Format the basic number portion, including the fractional digits. - void _formatFixed(dynamic number) { - dynamic integerPart; - int fractionPart; - int extraIntegerDigits; - var fractionDigits = _maximumFractionDigits; - var minFractionDigits = _minimumFractionDigits; - - var power = 0; - int digitMultiplier; - - // We have three possible pieces. First, the basic integer part. If this - // is a percent or permille, the additional 2 or 3 digits. Finally the - // fractional part. - // We avoid multiplying the number because it might overflow if we have - // a fixed-size integer type, so we extract each of the three as an - // integer pieces. - integerPart = _floor(number); - var fraction = number - integerPart; - if (fraction.toInt() != 0) { - // If the fractional part leftover is > 1, presumbly the number - // was too big for a fixed-size integer, so leave it as whatever - // it was - the obvious thing is a double. - integerPart = number; - fraction = 0; - } - - power = pow(10, fractionDigits) as int; - digitMultiplier = power; - - // Multiply out to the number of decimal places and the percent, then - // round. For fixed-size integer types this should always be zero, so - // multiplying is OK. - var remainingDigits = _round(fraction * digitMultiplier).toInt(); - - if (remainingDigits >= digitMultiplier) { - // Overflow into the main digits: 0.99 => 1.00 - integerPart++; - remainingDigits -= digitMultiplier; - } else if (_numberOfIntegerDigits(remainingDigits) > - _numberOfIntegerDigits(_floor(fraction * digitMultiplier).toInt())) { - // Fraction has been rounded (0.0996 -> 0.1). - fraction = remainingDigits / digitMultiplier; - } - - // Separate out the extra integer parts from the fraction part. - extraIntegerDigits = remainingDigits ~/ power; - fractionPart = remainingDigits % power; - - var integerDigits = _integerDigits(integerPart, extraIntegerDigits); - var digitLength = integerDigits.length; - var fractionPresent = - fractionDigits > 0 && (minFractionDigits > 0 || fractionPart > 0); - - if (_hasIntegerDigits(integerDigits)) { - // Add the padding digits to the regular digits so that we get grouping. - var padding = '0' * (_minimumIntegerDigits - digitLength); - integerDigits = '$padding$integerDigits'; - digitLength = integerDigits.length; - for (var i = 0; i < digitLength; i++) { - _addDigit(integerDigits.codeUnitAt(i)); - } - } else if (!fractionPresent) { - // If neither fraction nor integer part exists, just print zero. - _addZero(); - } - - _decimalSeparator(fractionPresent); - if (fractionPresent) { - _formatFractionPart((fractionPart + power).toString(), minFractionDigits); - } - } - - /// Helper to get the floor of a number which might not be num. This should - /// only ever be called with an argument which is positive, or whose abs() - /// is negative. The second case is the maximum negative value on a - /// fixed-length integer. Since they are integers, they are also their own - /// floor. - dynamic _floor(dynamic number) { - if (number.isNegative && !number.abs().isNegative) { - throw ArgumentError( - 'Internal error: expected positive number, got $number'); - } - return (number is num) ? number.floor() : number ~/ 1; - } - - /// Helper to round a number which might not be num. - dynamic _round(dynamic number) { - if (number is num) { - if (number.isInfinite) { - return _maxInt; - } else { - return number.round(); - } - } else if (number.remainder(1) == 0) { - // Not a normal number, but int-like, e.g. Int64 - return number; - } else { - // TODO(alanknight): Do this more efficiently. If IntX had floor and - // round we could avoid this. - var basic = _floor(number); - var fraction = (number - basic).toDouble().round(); - return fraction == 0 ? number : number + fraction; - } - } - - // Return the number of digits left of the decimal place in [number]. - static int _numberOfIntegerDigits(dynamic number) { - var simpleNumber = (number.toDouble() as double).abs(); - // It's unfortunate that we have to do this, but we get precision errors - // that affect the result if we use logs, e.g. 1000000 - if (simpleNumber < 10) return 1; - if (simpleNumber < 100) return 2; - if (simpleNumber < 1000) return 3; - if (simpleNumber < 10000) return 4; - if (simpleNumber < 100000) return 5; - if (simpleNumber < 1000000) return 6; - if (simpleNumber < 10000000) return 7; - if (simpleNumber < 100000000) return 8; - if (simpleNumber < 1000000000) return 9; - if (simpleNumber < 10000000000) return 10; - if (simpleNumber < 100000000000) return 11; - if (simpleNumber < 1000000000000) return 12; - if (simpleNumber < 10000000000000) return 13; - if (simpleNumber < 100000000000000) return 14; - if (simpleNumber < 1000000000000000) return 15; - if (simpleNumber < 10000000000000000) return 16; - if (simpleNumber < 100000000000000000) return 17; - if (simpleNumber < 1000000000000000000) return 18; - return 19; - } - - /// Compute the raw integer digits which will then be printed with - /// grouping and translated to localized digits. - String _integerDigits(dynamic integerPart, dynamic extraIntegerDigits) { - // If the integer part is larger than the maximum integer size - // (2^52 on Javascript, 2^63 on the VM) it will lose precision, - // so pad out the rest of it with zeros. - var paddingDigits = ''; - if (integerPart is num && integerPart > _maxInt) { - var howManyDigitsTooBig = - (log(integerPart) / log(10)).ceil() - _maxDigits; - num divisor = pow(10, howManyDigitsTooBig).round(); - // pow() produces 0 if the result is too large for a 64-bit int. - // If that happens, use a floating point divisor instead. - if (divisor == 0) divisor = pow(10.0, howManyDigitsTooBig); - paddingDigits = '0' * howManyDigitsTooBig.toInt(); - integerPart = (integerPart / divisor).truncate(); - } - - var extra = extraIntegerDigits == 0 ? '' : extraIntegerDigits.toString(); - var intDigits = _mainIntegerDigits(integerPart); - var paddedExtra = intDigits.isEmpty ? extra : extra.padLeft(0, '0'); - return '$intDigits$paddedExtra$paddingDigits'; - } - - /// The digit string of the integer part. This is the empty string if the - /// integer part is zero and otherwise is the toString() of the integer - /// part, stripping off any minus sign. - String _mainIntegerDigits(dynamic integer) { - if (integer == 0) return ''; - var digits = integer.toString(); - // If we have a fixed-length int representation, it can have a negative - // number whose negation is also negative, e.g. 2^-63 in 64-bit. - // Remove the minus sign. - return digits.startsWith('-') ? digits.substring(1) : digits; - } - - /// Format the part after the decimal place in a fixed point number. - void _formatFractionPart(String fractionPart, int minDigits) { - var fractionLength = fractionPart.length; - while (fractionPart.codeUnitAt(fractionLength - 1) == '0'.codeUnitAt(0) && - fractionLength > minDigits + 1) { - fractionLength--; - } - for (var i = 1; i < fractionLength; i++) { - _addDigit(fractionPart.codeUnitAt(i)); - } - } - - /// Print the decimal separator if appropriate. - void _decimalSeparator(bool fractionPresent) { - if (fractionPresent) { - _add(_symbols.DECIMAL_SEP); - } - } - - /// Return true if we have a main integer part which is printable, either - /// because we have digits left of the decimal point (this may include digits - /// which have been moved left because of percent or permille formatting), - /// or because the minimum number of printable digits is greater than 1. - bool _hasIntegerDigits(String digits) => - digits.isNotEmpty || _minimumIntegerDigits > 0; - - /// A group of methods that provide support for writing digits and other - /// required characters into [_buffer] easily. - void _add(String x) { - _buffer.write(x); - } - - void _addZero() { - _buffer.write(_symbols.ZERO_DIGIT); - } - - void _addDigit(int x) { - _buffer.writeCharCode(x + _zeroOffset); - } -} - -// Suppress naming issues as changes would be breaking. -// ignore_for_file: non_constant_identifier_names -class _NumberSymbols { - final String DECIMAL_SEP, ZERO_DIGIT; - - const _NumberSymbols({required this.DECIMAL_SEP, required this.ZERO_DIGIT}); -} diff --git a/dart/lib/src/utils/stacktrace_utils.dart b/dart/lib/src/utils/stacktrace_utils.dart deleted file mode 100644 index 99586ece73..0000000000 --- a/dart/lib/src/utils/stacktrace_utils.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:meta/meta.dart'; - -// A wrapper function around StackTrace.current so we can ignore it in the SDK -// crash detection. Otherwise, the SDK crash detection would have to ignore the -// method calling StackTrace.current, and it can't detect crashes in that -// method. -// You can read about the SDK crash detection here: -// https://github.com/getsentry/sentry/blob/master/src/sentry/utils/sdk_crashes/README.rst -@internal -StackTrace getCurrentStackTrace() => StackTrace.current; - -/// Regex that matches an “abs …” stack-frame line emitted by split-debug-info builds. -@internal -final absRegex = RegExp(r'^\s*#[0-9]+ +abs +([A-Fa-f0-9]+)'); - -/// Regex that matches the header of a stacktrace emitted by split-debug-info builds. -@internal -final buildIdRegex = RegExp(r"build_id[:=] *'([A-Fa-f0-9]+)'"); diff --git a/dart/lib/src/utils/tracing_utils.dart b/dart/lib/src/utils/tracing_utils.dart deleted file mode 100644 index bf283404ef..0000000000 --- a/dart/lib/src/utils/tracing_utils.dart +++ /dev/null @@ -1,128 +0,0 @@ -import '../../sentry.dart'; - -SentryTraceHeader generateSentryTraceHeader( - {SentryId? traceId, SpanId? spanId, bool? sampled}) { - traceId ??= SentryId.newId(); - spanId ??= SpanId.newId(); - return SentryTraceHeader(traceId, spanId, sampled: sampled); -} - -void addTracingHeadersToHttpHeader(Map headers, Hub hub, - {ISentrySpan? span}) { - if (span != null) { - addSentryTraceHeaderFromSpan(span, headers); - addBaggageHeaderFromSpan( - span, - headers, - log: hub.options.log, - ); - } else { - addSentryTraceHeaderFromScope(hub.scope, headers); - addBaggageHeaderFromScope(hub.scope, headers, log: hub.options.log); - } -} - -void addSentryTraceHeaderFromScope(Scope scope, Map headers) { - final propagationContext = scope.propagationContext; - final traceHeader = propagationContext.toSentryTrace(); - headers[traceHeader.name] = traceHeader.value; -} - -void addSentryTraceHeaderFromSpan( - ISentrySpan span, Map headers) { - final traceHeader = span.toSentryTrace(); - headers[traceHeader.name] = traceHeader.value; -} - -void addSentryTraceHeader( - SentryTraceHeader traceHeader, Map headers) { - headers[traceHeader.name] = traceHeader.value; -} - -void addBaggageHeaderFromScope( - Scope scope, - Map headers, { - SdkLogCallback? log, -}) { - final baggageHeader = scope.propagationContext.toBaggageHeader(); - if (baggageHeader != null) { - addBaggageHeader(baggageHeader, headers, log: log); - } -} - -void addBaggageHeaderFromSpan( - ISentrySpan span, - Map headers, { - SdkLogCallback? log, -}) { - final baggage = span.toBaggageHeader(); - if (baggage != null) { - addBaggageHeader(baggage, headers, log: log); - } -} - -void addBaggageHeader( - SentryBaggageHeader baggage, - Map headers, { - SdkLogCallback? log, -}) { - final currentValue = headers[baggage.name] as String? ?? ''; - - final currentBaggage = SentryBaggage.fromHeader( - currentValue, - log: log, - ); - final sentryBaggage = SentryBaggage.fromHeader( - baggage.value, - log: log, - ); - - // overwrite sentry's keys https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/#baggage - final filteredBaggageHeader = Map.from(currentBaggage.keyValues); - filteredBaggageHeader.removeWhere((key, value) => key.startsWith('sentry-')); - - final mergedBaggage = { - ...filteredBaggageHeader, - ...sentryBaggage.keyValues, - }; - - final newBaggage = SentryBaggage(mergedBaggage, log: log); - - headers[baggage.name] = newBaggage.toHeaderString(); -} - -bool containsTargetOrMatchesRegExp( - List tracePropagationTargets, String url) { - if (tracePropagationTargets.isEmpty) { - return false; - } - for (final target in tracePropagationTargets) { - if (url.contains(target)) { - return true; - } - try { - final regExp = RegExp(target, caseSensitive: false); - if (regExp.hasMatch(url)) { - return true; - } - } on FormatException { - // ignore invalid regex - continue; - } - } - return false; -} - -bool isValidSampleRate(double? sampleRate) { - if (sampleRate == null) { - return false; - } - return !sampleRate.isNaN && sampleRate >= 0.0 && sampleRate <= 1.0; -} - -bool isValidSampleRand(double? sampleRand) { - if (sampleRand == null) { - return false; - } - return !sampleRand.isNaN && sampleRand >= 0.0 && sampleRand < 1.0; -} diff --git a/dart/lib/src/utils/transport_utils.dart b/dart/lib/src/utils/transport_utils.dart deleted file mode 100644 index 3dae120d58..0000000000 --- a/dart/lib/src/utils/transport_utils.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:http/http.dart'; - -import '../client_reports/discard_reason.dart'; -import '../protocol.dart'; -import '../sentry_envelope.dart'; -import '../sentry_options.dart'; -import '../transport/data_category.dart'; - -class TransportUtils { - static void logResponse( - SentryOptions options, SentryEnvelope envelope, Response response, - {required String target}) { - if (response.statusCode != 200) { - if (options.debug) { - options.log( - SentryLevel.error, - 'Error, statusCode = ${response.statusCode}, body = ${response.body}', - ); - } - - if (response.statusCode >= 400 && response.statusCode != 429) { - for (final item in envelope.items) { - options.recorder.recordLostEvent( - DiscardReason.networkError, - DataCategory.fromItemType(item.header.type), - ); - - final originalObject = item.originalObject; - if (originalObject is SentryTransaction) { - options.recorder.recordLostEvent( - DiscardReason.networkError, - DataCategory.span, - count: originalObject.spans.length + 1, - ); - } - } - } - } else { - options.log( - SentryLevel.debug, - 'Envelope ${envelope.header.eventId ?? "--"} was sent successfully to $target.', - ); - } - } -} diff --git a/dart/lib/src/utils/url_details.dart b/dart/lib/src/utils/url_details.dart deleted file mode 100644 index 4cc47510a6..0000000000 --- a/dart/lib/src/utils/url_details.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:meta/meta.dart'; -import '../../sentry.dart'; - -/// Sanitized url data for sentry.io -@internal -class UrlDetails { - UrlDetails({this.url, this.query, this.fragment}); - - final String? url; - final String? query; - final String? fragment; - - static const _unknown = 'unknown'; - - late final urlOrFallback = - Uri.tryParse(url ?? _unknown)?.toString() ?? _unknown; - - void applyToSpan(ISentrySpan? span) { - if (span == null) { - return; - } - if (url != null) { - span.setData('url', url); - } - if (query != null) { - span.setData("http.query", query); - } - if (fragment != null) { - span.setData("http.fragment", fragment); - } - } -} diff --git a/dart/lib/src/version.dart b/dart/lib/src/version.dart deleted file mode 100644 index 6196fc630c..0000000000 --- a/dart/lib/src/version.dart +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/// Sentry.io has a concept of "SDK", which refers to the client library or -/// tool used to submit events to Sentry.io. -/// -/// This library contains Sentry.io SDK constants used by this package. -library; - -/// The SDK version reported to Sentry.io in the submitted events. -const String sdkVersion = '9.6.0-beta.1'; - -String sdkName(bool isWeb) => isWeb ? _browserSdkName : _ioSdkName; - -/// The default SDK name reported to Sentry.io in the submitted events. -const String _ioSdkName = 'sentry.dart'; - -/// The SDK name for web projects reported to Sentry.io in the submitted events. -const String _browserSdkName = '$_ioSdkName.browser'; - -/// The name of the SDK platform reported to Sentry.io in the submitted events. -/// -/// Used for IO version. -String sdkPlatform(bool isWeb) => isWeb ? _browserPlatform : _ioSdkPlatform; - -/// The name of the SDK platform reported to Sentry.io in the submitted events. -/// -/// Used for IO version. -const String _ioSdkPlatform = 'other'; - -/// Used to report browser Stacktrace to sentry. -const String _browserPlatform = 'javascript'; diff --git a/dart/pubspec.yaml b/dart/pubspec.yaml deleted file mode 100644 index 142b86ae9c..0000000000 --- a/dart/pubspec.yaml +++ /dev/null @@ -1,38 +0,0 @@ -name: sentry -version: 9.6.0-beta.1 -description: > - A crash reporting library for Dart that sends crash reports to Sentry.io. - This library supports Dart VM and Web. For Flutter consider sentry_flutter instead. -homepage: https://docs.sentry.io/platforms/dart/ -repository: https://github.com/getsentry/sentry-dart -issue_tracker: https://github.com/getsentry/sentry-dart/issues -documentation: https://docs.sentry.io/platforms/dart/ - -environment: - sdk: '>=3.5.0 <4.0.0' - -platforms: - android: - ios: - macos: - linux: - windows: - web: - -dependencies: - http: '>=0.13.0 <2.0.0' - meta: ^1.3.0 - stack_trace: ^1.10.0 - uuid: '>=3.0.0 <5.0.0' - collection: ^1.16.0 - web: ^1.1.0 - -dev_dependencies: - build_runner: ^2.3.0 - mockito: ^5.1.0 - lints: '>=2.0.0' - test: ^1.21.1 - yaml: ^3.1.0 # needed for version match (code and pubspec) - coverage: ^1.3.0 - intl: '>=0.17.0 <1.0.0' - version: ^3.0.2 diff --git a/dart/test/client_reports/client_report_recorder_test.dart b/dart/test/client_reports/client_report_recorder_test.dart deleted file mode 100644 index 3466d89f4d..0000000000 --- a/dart/test/client_reports/client_report_recorder_test.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:sentry/src/client_reports/discard_reason.dart'; -import 'package:sentry/src/transport/data_category.dart'; -import 'package:test/test.dart'; - -import 'package:sentry/src/client_reports/client_report_recorder.dart'; - -void main() { - group(ClientReportRecorder, () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('flush returns null when there was nothing recorded', () { - final sut = fixture.getSut(); - - final clientReport = sut.flush(); - - expect(clientReport, null); - }); - - test('flush returns client report with current date', () { - final sut = fixture.getSut(); - - sut.recordLostEvent(DiscardReason.rateLimitBackoff, DataCategory.error); - - final clientReport = sut.flush(); - - expect(clientReport?.timestamp, DateTime(0)); - }); - - test('record lost event', () { - final sut = fixture.getSut(); - - sut.recordLostEvent(DiscardReason.rateLimitBackoff, DataCategory.error); - sut.recordLostEvent(DiscardReason.rateLimitBackoff, DataCategory.error); - - final clientReport = sut.flush(); - - final event = clientReport?.discardedEvents - .firstWhere((element) => element.category == DataCategory.error); - - expect(event?.reason, DiscardReason.rateLimitBackoff); - expect(event?.category, DataCategory.error); - expect(event?.quantity, 2); - }); - - test('record outcomes with different categories recorded separately', () { - final sut = fixture.getSut(); - - sut.recordLostEvent(DiscardReason.rateLimitBackoff, DataCategory.error); - sut.recordLostEvent( - DiscardReason.rateLimitBackoff, DataCategory.transaction); - - final clientReport = sut.flush(); - - final first = clientReport?.discardedEvents - .firstWhere((event) => event.category == DataCategory.error); - - final second = clientReport?.discardedEvents - .firstWhere((event) => event.category == DataCategory.transaction); - - expect(first?.reason, DiscardReason.rateLimitBackoff); - expect(first?.category, DataCategory.error); - expect(first?.quantity, 1); - - expect(second?.reason, DiscardReason.rateLimitBackoff); - expect(second?.category, DataCategory.transaction); - expect(second?.quantity, 1); - }); - - test('calling flush multiple times returns null', () { - final sut = fixture.getSut(); - - sut.recordLostEvent(DiscardReason.rateLimitBackoff, DataCategory.error); - - sut.flush(); - final clientReport = sut.flush(); - - expect(clientReport, null); - }); - }); -} - -class Fixture { - final _dateTimeProvider = () { - return DateTime(0); - }; - - ClientReportRecorder getSut() { - return ClientReportRecorder(_dateTimeProvider); - } -} diff --git a/dart/test/client_reports/client_report_test.dart b/dart/test/client_reports/client_report_test.dart deleted file mode 100644 index 00f5736cc2..0000000000 --- a/dart/test/client_reports/client_report_test.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/src/client_reports/client_report.dart'; -import 'package:sentry/src/client_reports/discard_reason.dart'; -import 'package:sentry/src/client_reports/discarded_event.dart'; -import 'package:sentry/src/transport/data_category.dart'; -import 'package:test/test.dart'; -import 'package:sentry/src/utils.dart'; - -void main() { - group('json', () { - late Fixture fixture; - - setUp(() async { - fixture = Fixture(); - }); - - test('toJson', () { - final sut = fixture.getSut(); - final json = sut.toJson(); - - expect( - DeepCollectionEquality().equals(fixture.clientReportJson, json), - true, - ); - }); - }); -} - -class Fixture { - final timestamp = DateTime.fromMillisecondsSinceEpoch(0); - late Map clientReportJson; - - Fixture() { - clientReportJson = { - 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), - 'discarded_events': [ - { - 'reason': 'ratelimit_backoff', - 'category': 'error', - 'quantity': 2, - } - ], - }; - } - - ClientReport getSut() { - return ClientReport( - timestamp, - [ - DiscardedEvent(DiscardReason.rateLimitBackoff, DataCategory.error, 2), - ], - ); - } -} diff --git a/dart/test/contexts_test.dart b/dart/test/contexts_test.dart deleted file mode 100644 index 060207adf5..0000000000 --- a/dart/test/contexts_test.dart +++ /dev/null @@ -1,331 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:convert'; - -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - group(Contexts, () { - final testBootTime = DateTime.fromMicrosecondsSinceEpoch(0); - - final testDevice = SentryDevice( - name: 'testDevice', - family: 'testFamily', - model: 'testModel', - modelId: 'testModelId', - arch: 'testArch', - batteryLevel: 23, - orientation: SentryOrientation.landscape, - manufacturer: 'testOEM', - brand: 'testBrand', - screenDensity: 99.1, - screenDpi: 100, - online: false, - charging: true, - lowMemory: false, - simulator: true, - memorySize: 1234567, - freeMemory: 12345, - usableMemory: 9876, - storageSize: 1234567, - freeStorage: 1234567, - externalStorageSize: 98765, - externalFreeStorage: 98765, - bootTime: testBootTime, - ); - final testOS = SentryOperatingSystem(name: 'testOS'); - final testRuntimes = [ - SentryRuntime(name: 'testRT1', version: '1.0'), - SentryRuntime(name: 'testRT2', version: '2.3.1'), - ]; - final testApp = SentryApp(version: '1.2.3'); - final testBrowser = SentryBrowser(version: '12.3.4'); - - final gpu = SentryGpu(name: 'Radeon', version: '1'); - - final flags = SentryFeatureFlags( - values: [ - SentryFeatureFlag(flag: 'feature_flag_1', result: true), - SentryFeatureFlag(flag: 'feature_flag_2', result: false), - ], - ); - - final contexts = Contexts( - device: testDevice, - operatingSystem: testOS, - runtimes: testRuntimes, - app: testApp, - browser: testBrowser, - gpu: gpu, - flags: flags, - ) - ..['theme'] = {'value': 'material'} - ..['version'] = {'value': 9}; - - final contextsJson = { - 'device': { - 'name': 'testDevice', - 'family': 'testFamily', - 'model': 'testModel', - 'model_id': 'testModelId', - 'arch': 'testArch', - 'battery_level': 23.0, - 'orientation': 'landscape', - 'manufacturer': 'testOEM', - 'brand': 'testBrand', - 'screen_density': 99.1, - 'screen_dpi': 100, - 'online': false, - 'charging': true, - 'low_memory': false, - 'simulator': true, - 'memory_size': 1234567, - 'free_memory': 12345, - 'usable_memory': 9876, - 'storage_size': 1234567, - 'free_storage': 1234567, - 'external_storage_size': 98765, - 'external_free_storage': 98765, - 'boot_time': testBootTime.toIso8601String(), - }, - 'os': { - 'name': 'testOS', - }, - 'app': {'app_version': '1.2.3'}, - 'browser': {'version': '12.3.4'}, - 'gpu': {'name': 'Radeon', 'version': '1'}, - 'testrt1': {'name': 'testRT1', 'type': 'runtime', 'version': '1.0'}, - 'testrt2': {'name': 'testRT2', 'type': 'runtime', 'version': '2.3.1'}, - 'theme': {'value': 'material'}, - 'version': {'value': 9}, - 'flags': { - 'values': [ - {'flag': 'feature_flag_1', 'result': true}, - {'flag': 'feature_flag_2', 'result': false}, - ] - }, - }; - - test('serializes to JSON', () { - final event = SentryEvent(contexts: contexts); - - expect(event.toJson()['contexts'], contextsJson); - }); - - test('deserializes/serializes JSON', () { - final contexts = Contexts.fromJson(contextsJson); - final json = contexts.toJson(); - - expect( - DeepCollectionEquality().equals(contextsJson, json), - true, - ); - }); - - test('clone context', () { - // ignore: deprecated_member_use_from_same_package - final clone = contexts.clone(); - - expect(clone.app!.toJson(), contexts.app!.toJson()); - expect(clone.browser!.toJson(), contexts.browser!.toJson()); - expect(clone.device!.toJson(), contexts.device!.toJson()); - expect( - clone.operatingSystem!.toJson(), contexts.operatingSystem!.toJson()); - expect(clone.gpu!.toJson(), contexts.gpu!.toJson()); - expect(clone.flags!.toJson(), contexts.flags!.toJson()); - for (final element in contexts.runtimes) { - expect( - clone.runtimes.where( - (clone) => MapEquality().equals(element.toJson(), clone.toJson()), - ), - isNotEmpty, - ); - } - - expect(clone['theme'], {'value': 'material'}); - expect(clone['version'], {'value': 9}); - }); - - test('set runtimes', () { - final contexts = Contexts(); - contexts.runtimes = [ - SentryRuntime(name: 'testRT1', version: '1.0'), - SentryRuntime(name: 'testRT2', version: '2.0'), - ]; - expect(contexts.runtimes.length, 2); - expect(contexts.runtimes.first.name, 'testRT1'); - expect(contexts.runtimes.first.version, '1.0'); - expect(contexts.runtimes.last.name, 'testRT2'); - expect(contexts.runtimes.last.version, '2.0'); - }); - - test('copyWith with contexts does not throw', () { - final contexts = Contexts( - runtimes: [ - SentryRuntime(name: 'testRT1', version: '1.0'), - ], - ); - // ignore: deprecated_member_use_from_same_package - final copy = contexts.copyWith(); - copy.addRuntime(SentryRuntime(name: 'testRT2', version: '2.0')); - - expect(copy.runtimes.length, 2); - expect(copy.runtimes.last.name, 'testRT2'); - }); - - test('can add runtime if runtime setter unmodifiable', () { - final contexts = Contexts(); - contexts.runtimes = List.unmodifiable([ - SentryRuntime(name: 'testRT1', version: '1.0'), - ]); - contexts.addRuntime(SentryRuntime(name: 'testRT2', version: '2.0')); - - expect(contexts.runtimes.length, 2); - expect(contexts.runtimes.last.name, 'testRT2'); - }); - - test('can add runtime if runtime ctor unmodifiable', () { - final contexts = Contexts( - runtimes: List.unmodifiable([ - SentryRuntime(name: 'testRT1', version: '1.0'), - ]), - ); - contexts.addRuntime(SentryRuntime(name: 'testRT2', version: '2.0')); - - expect(contexts.runtimes.length, 2); - expect(contexts.runtimes.last.name, 'testRT2'); - }); - - test('set flags', () { - final contexts = Contexts(); - contexts.flags = SentryFeatureFlags( - values: [ - SentryFeatureFlag(flag: 'feature_flag_1', result: true), - SentryFeatureFlag(flag: 'feature_flag_2', result: false), - ], - ); - expect(contexts.flags!.toJson(), flags.toJson()); - }); - }); - - group('parse contexts', () { - test('should parse json context', () { - final contexts = Contexts.fromJson(jsonDecode(jsonContexts)); - expect( - MapEquality().equals( - contexts.operatingSystem!.toJson(), - { - 'build': '19H2', - 'rooted': false, - 'kernel_version': - 'Darwin Kernel Version 19.6.0: Mon Aug 31 22:12:52 PDT 2020; root:xnu-6153.141.2~1/RELEASE_X86_64', - 'name': 'iOS', - 'version': '14.2' - }, - ), - true, - ); - expect( - MapEquality().equals(contexts.device!.toJson(), { - 'simulator': true, - 'model_id': 'simulator', - 'arch': 'x86', - 'free_memory': 232132608, - 'family': 'iOS', - 'model': 'iPhone13,4', - 'memory_size': 17179869184, - 'storage_size': 1023683072000, - 'boot_time': '2020-11-18T13:28:11.000Z', - 'usable_memory': 17114120192 - }), - true, - ); - - expect( - MapEquality().equals( - contexts.app!.toJson(), - { - 'app_id': 'D533244D-985D-3996-9FC2-9FA353D28586', - 'app_name': 'sentry_flutter_example', - 'app_version': '0.1.2', - 'app_identifier': 'io.sentry.flutter.example', - 'app_start_time': '2020-11-18T13:56:58.000Z', - 'device_app_hash': '59ca66aa7ac0bdc3d82f77041643036f6323bd6d', - 'app_build': '3', - 'build_type': 'simulator', - }, - ), - true, - ); - expect( - MapEquality().equals(contexts.runtimes.first.toJson(), { - 'name': 'testRT1', - 'version': '1.0', - 'raw_description': 'runtime description RT1 1.0' - }), - true, - ); - expect( - MapEquality().equals(contexts.browser!.toJson(), {'version': '12.3.4'}), - true, - ); - expect( - MapEquality() - .equals(contexts.gpu!.toJson(), {'name': 'Radeon', 'version': '1'}), - true, - ); - }); - }); -} - -const jsonContexts = ''' -{ - "os": { - "build": "19H2", - "rooted": false, - "kernel_version": "Darwin Kernel Version 19.6.0: Mon Aug 31 22:12:52 PDT 2020; root:xnu-6153.141.2~1/RELEASE_X86_64", - "name": "iOS", - "version": "14.2" - }, - "device": { - "simulator": true, - "model_id": "simulator", - "arch": "x86", - "free_memory": 232132608, - "family": "iOS", - "model": "iPhone13,4", - "memory_size": 17179869184, - "storage_size": 1023683072000, - "boot_time": "2020-11-18T13:28:11Z", - "usable_memory": 17114120192 - }, - "app": { - "app_id": "D533244D-985D-3996-9FC2-9FA353D28586", - "app_version": "0.1.2", - "app_identifier": "io.sentry.flutter.example", - "app_start_time": "2020-11-18T13:56:58Z", - "device_app_hash": "59ca66aa7ac0bdc3d82f77041643036f6323bd6d", - "app_build": "3", - "build_type": "simulator", - "app_name": "sentry_flutter_example" - }, - "runtime": - { - "name":"testRT1", - "version":"1.0", - "raw_description":"runtime description RT1 1.0" - }, - "browser": {"version": "12.3.4"}, - "gpu": {"name": "Radeon", "version": "1"}, - "flags": { - "values": [ - {"flag": "feature_flag_1", "result": true}, - {"flag": "feature_flag_2", "result": false} - ] - } -} -'''; diff --git a/dart/test/diagnostic_logger_test.dart b/dart/test/diagnostic_logger_test.dart deleted file mode 100644 index 3bd5c46fe1..0000000000 --- a/dart/test/diagnostic_logger_test.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/diagnostic_log.dart'; -import 'package:test/test.dart'; - -import 'test_utils.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - group(DiagnosticLog, () { - test('does not log if debug is disabled', () { - fixture.options.debug = false; - - fixture.getSut().log(SentryLevel.error, 'foobar'); - - expect(fixture.loggedMessage, isNull); - }); - - test('logs if debug is enabled', () { - fixture.options.debug = true; - - fixture.getSut().log(SentryLevel.error, 'foobar'); - - expect(fixture.loggedMessage, 'foobar'); - }); - - test('does not log if level is too low', () { - fixture.options.debug = true; - fixture.options.diagnosticLevel = SentryLevel.error; - - fixture.getSut().log(SentryLevel.warning, 'foobar'); - - expect(fixture.loggedMessage, isNull); - }); - - test('always logs fatal', () { - fixture.options.debug = false; - - fixture.getSut().log(SentryLevel.fatal, 'foobar'); - - expect(fixture.loggedMessage, 'foobar'); - }); - }); -} - -class Fixture { - var options = defaultTestOptions(); - - Object? loggedMessage; - - DiagnosticLog getSut() { - return DiagnosticLog(mockLogger, options); - } - - void mockLogger( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - loggedMessage = message; - } -} diff --git a/dart/test/environment_test.dart b/dart/test/environment_test.dart deleted file mode 100644 index d955f931e2..0000000000 --- a/dart/test/environment_test.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'mocks/mock_environment_variables.dart'; -import 'test_utils.dart'; - -void main() { - // See https://docs.sentry.io/platforms/dart/configuration/options/ - // and https://github.com/getsentry/sentry-dart/issues/306 - group('Environment Variables', () { - tearDown(() async { - await Sentry.close(); - }); - - test('SentryOptions are not overriden by environment', () async { - final options = defaultTestOptions(); - options.release = 'release-1.2.3'; - options.dist = 'foo'; - options.environment = 'prod'; - options.environmentVariables = MockEnvironmentVariables( - dsn: 'foo-bar', - environment: 'staging', - release: 'release-9.8.7', - dist: 'bar', - ); - - await Sentry.init( - (options) => options, - options: options, - ); - - expect(options.dsn, testDsn); - expect(options.environment, 'prod'); - expect(options.release, 'release-1.2.3'); - expect(options.dist, 'foo'); - }); - - test('SentryOptions are overriden by environment', () async { - final options = defaultTestOptions()..dsn = null; - options.environmentVariables = MockEnvironmentVariables( - dsn: fakeDsn, - environment: 'staging', - release: 'release-9.8.7', - dist: 'bar', - ); - - await Sentry.init( - (options) => options, - options: options, - ); - - expect(options.dsn, fakeDsn); - expect(options.environment, 'staging'); - expect(options.release, 'release-9.8.7'); - expect(options.dist, 'bar'); - }); - }); -} diff --git a/dart/test/event_processor/deduplication_event_processor_test.dart b/dart/test/event_processor/deduplication_event_processor_test.dart deleted file mode 100644 index de576bafd4..0000000000 --- a/dart/test/event_processor/deduplication_event_processor_test.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/event_processor/deduplication_event_processor.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; -import '../mocks/mock_hub.dart'; -import '../mocks/mock_transport.dart'; -import '../test_utils.dart'; - -void main() { - group('$DeduplicationEventProcessor', () { - final fixture = Fixture(); - - test('deduplicates if enabled', () { - final sut = fixture.getSut(true); - var ogEvent = _createEvent('foo'); - - expect(sut.apply(ogEvent, Hint()), isNotNull); - expect(sut.apply(ogEvent, Hint()), isNull); - }); - - test('does not deduplicate if disabled', () { - final sut = fixture.getSut(false); - var ogEvent = _createEvent('foo'); - - expect(sut.apply(ogEvent, Hint()), isNotNull); - expect(sut.apply(ogEvent, Hint()), isNotNull); - }); - - test('does not deduplicate if different events', () { - final sut = fixture.getSut(true); - var fooEvent = _createEvent('foo'); - var barEvent = _createEvent('bar'); - - expect(sut.apply(fooEvent, Hint()), isNotNull); - expect(sut.apply(barEvent, Hint()), isNotNull); - }); - - test('does not deduplicate transaction', () { - final sut = fixture.getSut(true); - final transaction = _createTransaction(fixture.hub); - - expect(sut.apply(transaction, Hint()), isNotNull); - expect(sut.apply(transaction, Hint()), isNotNull); - }); - - test('exceptions to keep for deduplication', () { - final sut = fixture.getSut(true, 2); - - var fooEvent = _createEvent('foo'); - var barEvent = _createEvent('bar'); - var fooBarEvent = _createEvent('foo bar'); - - expect(sut.apply(fooEvent, Hint()), isNotNull); - expect(sut.apply(barEvent, Hint()), isNotNull); - expect(sut.apply(fooBarEvent, Hint()), isNotNull); - expect(sut.apply(fooEvent, Hint()), isNotNull); - }); - - test('integration test', () async { - Future innerThrowingMethod() async { - try { - throw Exception('foo bar'); - } catch (e, stackTrace) { - await Sentry.captureException(e, stackTrace: stackTrace); - rethrow; - } - } - - Future outerThrowingMethod() async { - try { - await innerThrowingMethod(); - } catch (e, stackTrace) { - await Sentry.captureException(e, stackTrace: stackTrace); - } - } - - final transport = MockTransport(); - - await Sentry.init( - (options) { - options.dsn = fakeDsn; - options.transport = transport; - options.enableDeduplication = true; - }, - options: defaultTestOptions(), - ); - - // The test doesn't work if `outerTestMethod` is passed as - // `appRunner` callback - await outerThrowingMethod(); - - expect(transport.envelopes.length, 1); - - await Sentry.close(); - }); - }); -} - -SentryEvent _createEvent(String message) { - return SentryEvent(throwable: Exception(message)); -} - -SentryTransaction _createTransaction(Hub hub) { - final context = SentryTransactionContext('name', 'op'); - - final tracer = SentryTracer(context, hub); - return SentryTransaction(tracer); -} - -class Fixture { - final hub = MockHub(); - - DeduplicationEventProcessor getSut(bool enabled, - [int? maxDeduplicationItems]) { - final options = defaultTestOptions() - ..enableDeduplication = enabled - ..maxDeduplicationItems = maxDeduplicationItems ?? 5; - - return DeduplicationEventProcessor(options); - } -} diff --git a/dart/test/event_processor/enricher/io_enricher_test.dart b/dart/test/event_processor/enricher/io_enricher_test.dart deleted file mode 100644 index 42460a3d81..0000000000 --- a/dart/test/event_processor/enricher/io_enricher_test.dart +++ /dev/null @@ -1,268 +0,0 @@ -@TestOn('vm') -library; - -import 'dart:io'; - -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/event_processor/enricher/io_enricher_event_processor.dart'; -import 'package:sentry/src/platform/mock_platform.dart'; -import 'package:test/test.dart'; - -import '../../mocks.dart'; -import '../../test_utils.dart'; - -import 'package:sentry/src/utils/_io_get_sentry_operating_system.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('adds dart runtime', () async { - final enricher = fixture.getSut(); - final event = await enricher.apply(SentryEvent(), Hint()); - - expect(event?.contexts.runtimes, isNotEmpty); - final dartRuntime = event?.contexts.runtimes - .firstWhere((element) => element.name == 'Dart'); - expect(dartRuntime?.name, 'Dart'); - expect(dartRuntime?.rawDescription, isNotNull); - expect(dartRuntime!.version.toString(), isNot(Platform.version)); - expect(Platform.version, contains(dartRuntime.version.toString())); - }); - - test('does add to existing runtimes', () async { - final runtime = SentryRuntime(name: 'foo', version: 'bar'); - var event = SentryEvent(contexts: Contexts(runtimes: [runtime])); - final enricher = fixture.getSut(); - - event = (await enricher.apply(event, Hint()))!; - - expect(event.contexts.runtimes.contains(runtime), true); - // second runtime is Dart runtime - expect(event.contexts.runtimes.length, 2); - }); - - group('adds device, os and culture', () { - for (final hasNativeIntegration in [true, false]) { - test('native=$hasNativeIntegration', () async { - final enricher = - fixture.getSut(hasNativeIntegration: hasNativeIntegration); - final event = await enricher.apply(SentryEvent(), Hint()); - - expect(event?.contexts.device, isNotNull); - expect(event?.contexts.operatingSystem, isNotNull); - expect(event?.contexts.culture, isNotNull); - }); - } - }); - - test('device has no name if sendDefaultPii = false', () async { - final enricher = fixture.getSut(); - final event = await enricher.apply(SentryEvent(), Hint()); - - expect(event?.contexts.device?.name, isNull); - }); - - test('device has name if sendDefaultPii = true', () async { - final enricher = fixture.getSut(includePii: true); - final event = await enricher.apply(SentryEvent(), Hint()); - - expect(event?.contexts.device?.name, isNotNull); - }); - - test('culture has locale and timezone', () async { - final enricher = fixture.getSut(); - final event = await enricher.apply(SentryEvent(), Hint()); - - expect(event?.contexts.culture?.locale, isNotNull); - expect(event?.contexts.culture?.timezone, isNotNull); - }); - - test('os has name and version', () async { - final enricher = fixture.getSut(); - final event = await enricher.apply(SentryEvent(), Hint()); - - expect(event?.contexts.operatingSystem?.name, isNotNull); - if (Platform.isLinux) { - expect(event?.contexts.operatingSystem?.kernelVersion, isNotNull); - } else { - expect(event?.contexts.operatingSystem?.version, isNotNull); - } - }); - - group('os info parsing', () { - // See docs from [Platform.operatingSystemVersion]: - /// A string representing the version of the operating system or platform. - /// - /// The format of this string will vary by operating system, platform and - /// version and is not suitable for parsing. For example: - /// "Linux 5.11.0-1018-gcp #20~20.04.2-Ubuntu SMP Fri Sep 3 01:01:37 UTC 2021" - /// "Version 14.5 (Build 18E182)" - /// '"Windows 10 Pro" 10.0 (Build 19043)' - - Map parse(String name, String rawDescription) => - getSentryOperatingSystem(name: name, rawDescription: rawDescription) - .toJson(); - - test('android', () { - expect(parse('android', 'LYA-L29 10.1.0.289(C432E7R1P5)'), { - 'raw_description': 'LYA-L29 10.1.0.289(C432E7R1P5)', - 'name': 'Android', - 'build': 'LYA-L29 10.1.0.289(C432E7R1P5)', - }); - expect(parse('android', 'TE1A.220922.010'), { - 'raw_description': 'TE1A.220922.010', - 'name': 'Android', - 'build': 'TE1A.220922.010', - }); - }); - - test('linux', () { - expect( - parse('linux', - 'Linux 5.11.0-1018-gcp #20~20.04.2-Ubuntu SMP Fri Sep 3 01:01:37 UTC 2021'), - { - 'raw_description': - 'Linux 5.11.0-1018-gcp #20~20.04.2-Ubuntu SMP Fri Sep 3 01:01:37 UTC 2021', - 'name': 'Linux', - 'kernel_version': '5.11.0-1018-gcp', - 'build': '#20~20.04.2-Ubuntu SMP Fri Sep 3 01:01:37 UTC 2021', - }); - }); - - test('ios', () { - expect(parse('ios', 'Version 14.5 (Build 18E182)'), { - 'raw_description': 'Version 14.5 (Build 18E182)', - 'name': 'iOS', - 'version': '14.5', - 'build': '18E182', - }); - }); - - test('macos', () { - expect(parse('macos', 'Version 14.5 (Build 18E182)'), { - 'raw_description': 'Version 14.5 (Build 18E182)', - 'name': 'macOS', - 'version': '14.5', - 'build': '18E182', - }); - }); - - test('windows', () { - expect(parse('windows', '"Windows 10 Pro" 10.0 (Build 19043)'), { - 'raw_description': '"Windows 10 Pro" 10.0 (Build 19043)', - 'name': 'Windows', - 'version': '10.0', - 'build': '19043', - }); - }); - }); - - test('adds Dart context with PII', () async { - final enricher = fixture.getSut(includePii: true); - final event = await enricher.apply(SentryEvent(), Hint()); - - final dartContext = event?.contexts['dart_context']; - expect(dartContext, isNotNull); - // Getting the executable sometimes throws - //expect(dartContext['executable'], isNotNull); - expect(dartContext['resolved_executable'], isNotNull); - expect(dartContext['script'], isNotNull); - // package_config and executable_arguments are optional - }); - - test('adds Dart context without PII', () async { - final enricher = fixture.getSut(includePii: false); - final event = await enricher.apply(SentryEvent(), Hint()); - - final dartContext = event?.contexts['dart_context']; - expect(dartContext, isNotNull); - expect(dartContext['compile_mode'], isNotNull); - expect(dartContext['executable'], isNull); - expect(dartContext['resolved_executable'], isNull); - expect(dartContext['script'], isNull); - // package_config and executable_arguments are optional - // and Platform is not mockable - }); - - test('does not override event', () async { - final fakeEvent = SentryEvent( - contexts: Contexts( - device: SentryDevice( - name: 'device_name', - ), - operatingSystem: SentryOperatingSystem( - name: 'sentry_os', - version: 'best version', - ), - culture: SentryCulture( - locale: 'de', - timezone: 'timezone', - ), - ), - ); - - final enricher = fixture.getSut( - includePii: true, - hasNativeIntegration: false, - ); - - final event = await enricher.apply(fakeEvent, Hint()); - - // contexts.device - expect( - event?.contexts.device?.name, - fakeEvent.contexts.device?.name, - ); - // contexts.culture - expect( - event?.contexts.culture?.locale, - fakeEvent.contexts.culture?.locale, - ); - expect( - event?.contexts.culture?.timezone, - fakeEvent.contexts.culture?.timezone, - ); - // contexts.operatingSystem - expect( - event?.contexts.operatingSystem?.name, - fakeEvent.contexts.operatingSystem?.name, - ); - expect( - event?.contexts.operatingSystem?.version, - fakeEvent.contexts.operatingSystem?.version, - ); - }); - - test('$IoEnricherEventProcessor gets added on init', () async { - final options = defaultTestOptions(); - await Sentry.init( - (options) { - options.dsn = fakeDsn; - }, - options: options, - ); - await Sentry.close(); - - final ioEnricherCount = - options.eventProcessors.whereType().length; - expect(ioEnricherCount, 1); - }); -} - -class Fixture { - IoEnricherEventProcessor getSut({ - bool hasNativeIntegration = false, - bool includePii = false, - }) { - final options = defaultTestOptions() - ..platform = - hasNativeIntegration ? MockPlatform.iOS() : MockPlatform.fuchsia() - ..sendDefaultPii = includePii; - - return IoEnricherEventProcessor(options); - } -} diff --git a/dart/test/event_processor/enricher/io_platform_memory_test.dart b/dart/test/event_processor/enricher/io_platform_memory_test.dart deleted file mode 100644 index 38b30e871d..0000000000 --- a/dart/test/event_processor/enricher/io_platform_memory_test.dart +++ /dev/null @@ -1,43 +0,0 @@ -@TestOn('vm') -library; - -import 'dart:io'; - -import 'package:sentry/src/event_processor/enricher/io_platform_memory.dart'; -import 'package:test/test.dart'; - -import '../../test_utils.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('total physical memory', () async { - final sut = fixture.getSut(); - final totalPhysicalMemory = await sut.getTotalPhysicalMemory(); - - switch (Platform.operatingSystem) { - case 'linux': - expect(totalPhysicalMemory, isNotNull); - expect(totalPhysicalMemory! > 0, true); - break; - case 'windows': - expect(totalPhysicalMemory, isNotNull); - expect(totalPhysicalMemory! > 0, true); - break; - default: - expect(totalPhysicalMemory, isNull); - } - }); -} - -class Fixture { - var options = defaultTestOptions(); - - PlatformMemory getSut() { - return PlatformMemory(options); - } -} diff --git a/dart/test/event_processor/enricher/web_enricher_test.dart b/dart/test/event_processor/enricher/web_enricher_test.dart deleted file mode 100644 index 1c3639ec42..0000000000 --- a/dart/test/event_processor/enricher/web_enricher_test.dart +++ /dev/null @@ -1,207 +0,0 @@ -@TestOn('browser') -library; - -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/event_processor/enricher/web_enricher_event_processor.dart'; -import 'package:sentry/src/platform/mock_platform.dart'; -import 'package:test/test.dart'; - -import '../../mocks.dart'; -import '../../test_utils.dart'; - -// can be tested on command line with -// `dart test -p chrome --name web_enricher` -void main() { - group('web_enricher', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('add path as transaction if transaction is null', () { - var enricher = fixture.getSut(); - final event = enricher.apply(SentryEvent(), Hint()); - - expect(event?.transaction, isNotNull); - }); - - test("don't overwrite transaction", () { - var enricher = fixture.getSut(); - final event = enricher.apply(SentryEvent(transaction: 'foobar'), Hint()); - - expect(event?.transaction, 'foobar'); - }); - - test('add request with user-agent header', () { - var enricher = fixture.getSut(); - final event = enricher.apply(SentryEvent(), Hint()); - - expect(event?.request?.headers['User-Agent'], isNotNull); - expect(event?.request?.url, isNotNull); - }); - - test('adds header to request if request already exists', () { - var event = SentryEvent( - request: SentryRequest( - url: 'foo.bar', - headers: { - 'foo': 'bar', - }, - ), - ); - var enricher = fixture.getSut(); - event = enricher.apply(event, Hint())!; - - expect(event.request?.headers['User-Agent'], isNotNull); - expect(event.request?.headers['foo'], 'bar'); - expect(event.request?.url, 'foo.bar'); - }); - - test('does not add auth headers to request', () { - var event = SentryEvent( - request: SentryRequest( - url: 'foo.bar', - headers: { - 'Authorization': 'foo', - 'authorization': 'bar', - }, - ), - ); - var enricher = fixture.getSut(); - event = enricher.apply(event, Hint())!; - - expect(event.request?.headers['Authorization'], isNull); - expect(event.request?.headers['authorization'], isNull); - }); - - test('user-agent is not overridden if already present', () { - var event = SentryEvent( - request: SentryRequest( - url: 'foo.bar', - headers: { - 'User-Agent': 'best browser agent', - }, - ), - ); - var enricher = fixture.getSut(); - event = enricher.apply(event, Hint())!; - - expect(event.request?.headers['User-Agent'], 'best browser agent'); - expect(event.request?.url, 'foo.bar'); - }); - - test('adds device and os', () { - var enricher = fixture.getSut(); - final event = enricher.apply(SentryEvent(), Hint()); - - expect(event?.contexts.device, isNotNull); - }); - - test('adds Dart context', () { - final enricher = fixture.getSut(); - final event = enricher.apply(SentryEvent(), Hint()); - - final dartContext = event?.contexts['dart_context']; - expect(dartContext, isNotNull); - expect(dartContext['compile_mode'], isNotNull); - }); - - test('device has screendensity', () { - var enricher = fixture.getSut(); - final event = enricher.apply(SentryEvent(), Hint()); - - expect(event?.contexts.device?.screenDensity, isNotNull); - }); - - test('culture has timezone', () { - var enricher = fixture.getSut(); - final event = enricher.apply(SentryEvent(), Hint()); - - expect(event?.contexts.culture?.timezone, isNotNull); - }); - - test('does not override event', () { - final fakeEvent = SentryEvent( - contexts: Contexts( - device: SentryDevice( - online: false, - memorySize: 200, - orientation: SentryOrientation.landscape, - screenHeightPixels: 1080, - screenWidthPixels: 1920, - screenDensity: 2, - ), - operatingSystem: SentryOperatingSystem( - name: 'sentry_os', - ), - culture: SentryCulture( - timezone: 'foo_timezone', - ), - ), - ); - - final enricher = fixture.getSut(); - - final event = enricher.apply(fakeEvent, Hint()); - - // contexts.device - expect( - event?.contexts.device?.online, - fakeEvent.contexts.device?.online, - ); - expect( - event?.contexts.device?.memorySize, - fakeEvent.contexts.device?.memorySize, - ); - expect( - event?.contexts.device?.orientation, - fakeEvent.contexts.device?.orientation, - ); - expect( - event?.contexts.device?.screenHeightPixels, - fakeEvent.contexts.device?.screenHeightPixels, - ); - expect( - event?.contexts.device?.screenWidthPixels, - fakeEvent.contexts.device?.screenWidthPixels, - ); - expect( - event?.contexts.device?.screenDensity, - fakeEvent.contexts.device?.screenDensity, - ); - // contexts.culture - expect( - event?.contexts.culture?.timezone, - fakeEvent.contexts.culture?.timezone, - ); - // contexts.operatingSystem - expect( - event?.contexts.operatingSystem?.name, - fakeEvent.contexts.operatingSystem?.name, - ); - }); - - test('$WebEnricherEventProcessor gets added on init', () async { - late SentryOptions sentryOptions; - await Sentry.init( - (options) { - options.dsn = fakeDsn; - sentryOptions = options; - }, - ); - await Sentry.close(); - - expect(sentryOptions.eventProcessors.map((e) => e.runtimeType.toString()), - contains('$WebEnricherEventProcessor')); - }); - }); -} - -class Fixture { - WebEnricherEventProcessor getSut() { - final options = defaultTestOptions() - ..platform = MockPlatform.fuchsia(); // Does not have native integration - return enricherEventProcessor(options) as WebEnricherEventProcessor; - } -} diff --git a/dart/test/event_processor/exception/exception_group_event_processor_test.dart b/dart/test/event_processor/exception/exception_group_event_processor_test.dart deleted file mode 100644 index b42f600c38..0000000000 --- a/dart/test/event_processor/exception/exception_group_event_processor_test.dart +++ /dev/null @@ -1,453 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import 'package:sentry/src/event_processor/exception/exception_group_event_processor.dart'; - -import '../../test_utils.dart'; - -void main() { - group(ExceptionGroupEventProcessor, () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('does not group exceptions if groupExceptions is false', () { - final throwableA = Exception('ExceptionA'); - final exceptionA = SentryException( - type: 'ExceptionA', - value: 'ExceptionA', - throwable: throwableA, - mechanism: Mechanism(type: 'foo'), - ); - final throwableB = Exception('ExceptionB'); - final exceptionB = SentryException( - type: 'ExceptionB', - value: 'ExceptionB', - throwable: throwableB, - ); - exceptionA.addException(exceptionB); - - var event = SentryEvent( - throwable: throwableA, - exceptions: [exceptionA], - ); - - final sut = fixture.getSut(groupExceptions: false); - event = (sut.apply(event, Hint()))!; - - final sentryExceptionA = event.exceptions![0]; - final sentryExceptionB = event.exceptions![1]; - - expect(sentryExceptionB.throwable, throwableB); - expect(sentryExceptionB.mechanism?.type, isNull); - expect(sentryExceptionB.mechanism?.isExceptionGroup, isNull); - expect(sentryExceptionB.mechanism?.exceptionId, isNull); - expect(sentryExceptionB.mechanism?.parentId, isNull); - - expect(sentryExceptionA.throwable, throwableA); - expect(sentryExceptionA.mechanism?.type, "foo"); - expect(sentryExceptionA.mechanism?.isExceptionGroup, isNull); - expect(sentryExceptionA.mechanism?.exceptionId, isNull); - expect(sentryExceptionA.mechanism?.parentId, isNull); - }); - - test('applies grouping to exception with children', () { - final throwableA = Exception('ExceptionA'); - final exceptionA = SentryException( - type: 'ExceptionA', - value: 'ExceptionA', - throwable: throwableA, - ); - final throwableB = Exception('ExceptionB'); - final exceptionB = SentryException( - type: 'ExceptionB', - value: 'ExceptionB', - throwable: throwableB, - ); - exceptionA.addException(exceptionB); - - var event = SentryEvent( - throwable: throwableA, - exceptions: [exceptionA], - ); - - final sut = fixture.getSut(groupExceptions: true); - event = (sut.apply(event, Hint()))!; - - final sentryExceptionB = event.exceptions![0]; - final sentryExceptionA = event.exceptions![1]; - - expect(sentryExceptionB.throwable, throwableB); - expect(sentryExceptionB.mechanism?.type, "chained"); - expect(sentryExceptionB.mechanism?.isExceptionGroup, isNull); - expect(sentryExceptionB.mechanism?.exceptionId, 1); - expect(sentryExceptionB.mechanism?.parentId, 0); - - expect(sentryExceptionA.throwable, throwableA); - expect(sentryExceptionA.mechanism?.type, "generic"); - expect(sentryExceptionA.mechanism?.isExceptionGroup, isTrue); - expect(sentryExceptionA.mechanism?.exceptionId, 0); - expect(sentryExceptionA.mechanism?.parentId, isNull); - }); - - test('applies no grouping if there is no exception', () { - final event = SentryEvent(); - final sut = fixture.getSut(groupExceptions: true); - - final result = sut.apply(event, Hint()); - - expect(result, event); - expect(event.throwable, isNull); - expect(event.exceptions, isNull); - }); - - test('applies no grouping if there is already a list of exceptions', () { - final event = SentryEvent( - exceptions: [ - SentryException( - type: 'ExceptionA', - value: 'ExceptionA', - throwable: Exception('ExceptionA')), - SentryException( - type: 'ExceptionB', - value: 'ExceptionB', - throwable: Exception('ExceptionB')), - ], - ); - final sut = fixture.getSut(groupExceptions: true); - - final result = sut.apply(event, Hint()); - - final sentryExceptionA = result?.exceptions![0]; - final sentryExceptionB = result?.exceptions![1]; - - expect(sentryExceptionA?.type, 'ExceptionA'); - expect(sentryExceptionB?.type, 'ExceptionB'); - }); - }); - - group('flatten', () { - late Fixture fixture; - - final sentryException = SentryException( - type: 'type', - value: 'value', - module: 'module', - stackTrace: SentryStackTrace(frames: [SentryStackFrame(absPath: 'abs')]), - mechanism: Mechanism(type: 'type'), - threadId: 1, - ); - - setUp(() { - fixture = Fixture(); - }); - - test('will flatten exception with nested chained exceptions', () { - // ignore: deprecated_member_use_from_same_package - final origin = sentryException.copyWith( - value: 'origin', - ); - // ignore: deprecated_member_use_from_same_package - final originChild = sentryException.copyWith( - value: 'originChild', - ); - origin.addException(originChild); - - // ignore: deprecated_member_use_from_same_package - final originChildChild = sentryException.copyWith( - value: 'originChildChild', - ); - originChild.addException(originChildChild); - - final sut = fixture.getSut(groupExceptions: true); - var event = SentryEvent(exceptions: [origin]); - event = sut.apply(event, Hint())!; - final flattened = event.exceptions ?? []; - - expect(flattened.length, 3); - - expect(flattened[2].value, 'origin'); - expect(flattened[2].mechanism?.isExceptionGroup, isTrue); - expect(flattened[2].mechanism?.source, isNull); - expect(flattened[2].mechanism?.exceptionId, 0); - expect(flattened[2].mechanism?.parentId, null); - - expect(flattened[1].value, 'originChild'); - expect(flattened[1].mechanism?.isExceptionGroup, isTrue); - expect(flattened[1].mechanism?.source, isNull); - expect(flattened[1].mechanism?.exceptionId, 1); - expect(flattened[1].mechanism?.parentId, 0); - - expect(flattened[0].value, 'originChildChild'); - expect(flattened[0].mechanism?.isExceptionGroup, isNull); - expect(flattened[0].mechanism?.source, isNull); - expect(flattened[0].mechanism?.exceptionId, 2); - expect(flattened[0].mechanism?.parentId, 1); - }); - - test('will flatten exception with nested parallel exceptions', () { - // ignore: deprecated_member_use_from_same_package - final origin = sentryException.copyWith( - value: 'origin', - ); - // ignore: deprecated_member_use_from_same_package - final originChild = sentryException.copyWith( - value: 'originChild', - ); - origin.addException(originChild); - // ignore: deprecated_member_use_from_same_package - final originChild2 = sentryException.copyWith( - value: 'originChild2', - ); - origin.addException(originChild2); - - final sut = fixture.getSut(groupExceptions: true); - var event = SentryEvent(exceptions: [origin]); - event = sut.apply(event, Hint())!; - final flattened = event.exceptions ?? []; - - expect(flattened.length, 3); - - expect(flattened[2].value, 'origin'); - expect(flattened[2].mechanism?.isExceptionGroup, true); - expect(flattened[2].mechanism?.source, isNull); - expect(flattened[2].mechanism?.exceptionId, 0); - expect(flattened[2].mechanism?.parentId, null); - - expect(flattened[1].value, 'originChild'); - expect(flattened[1].mechanism?.source, isNull); - expect(flattened[1].mechanism?.exceptionId, 1); - expect(flattened[1].mechanism?.parentId, 0); - - expect(flattened[0].value, 'originChild2'); - expect(flattened[0].mechanism?.source, isNull); - expect(flattened[0].mechanism?.exceptionId, 2); - expect(flattened[0].mechanism?.parentId, 0); - }); - - test('will flatten rfc example', () { - // try: - // raise RuntimeError("something") - // except: - // raise ExceptionGroup("nested", - // [ - // ValueError(654), - // ExceptionGroup("imports", - // [ - // ImportError("no_such_module"), - // ModuleNotFoundError("another_module"), - // ] - // ), - // TypeError("int"), - // ] - // ) - - // https://github.com/getsentry/rfcs/blob/main/text/0079-exception-groups.md#example-event - // In the example, the runtime error is inserted as the first exception in the outer exception group. - - // ignore: deprecated_member_use_from_same_package - final exceptionGroupNested = sentryException.copyWith( - value: 'ExceptionGroup', - ); - // ignore: deprecated_member_use_from_same_package - final runtimeError = sentryException.copyWith( - value: 'RuntimeError', - // ignore: deprecated_member_use_from_same_package - mechanism: sentryException.mechanism?.copyWith(source: '__source__'), - ); - exceptionGroupNested.addException(runtimeError); - // ignore: deprecated_member_use_from_same_package - final valueError = sentryException.copyWith( - value: 'ValueError', - // ignore: deprecated_member_use_from_same_package - mechanism: sentryException.mechanism?.copyWith(source: 'exceptions[0]'), - ); - exceptionGroupNested.addException(valueError); - - // ignore: deprecated_member_use_from_same_package - final exceptionGroupImports = sentryException.copyWith( - value: 'ExceptionGroup', - // ignore: deprecated_member_use_from_same_package - mechanism: sentryException.mechanism?.copyWith(source: 'exceptions[1]'), - ); - exceptionGroupNested.addException(exceptionGroupImports); - - // ignore: deprecated_member_use_from_same_package - final importError = sentryException.copyWith( - value: 'ImportError', - // ignore: deprecated_member_use_from_same_package - mechanism: sentryException.mechanism?.copyWith(source: 'exceptions[0]'), - ); - exceptionGroupImports.addException(importError); - - // ignore: deprecated_member_use_from_same_package - final moduleNotFoundError = sentryException.copyWith( - value: 'ModuleNotFoundError', - // ignore: deprecated_member_use_from_same_package - mechanism: sentryException.mechanism?.copyWith(source: 'exceptions[1]'), - ); - exceptionGroupImports.addException(moduleNotFoundError); - - // ignore: deprecated_member_use_from_same_package - final typeError = sentryException.copyWith( - value: 'TypeError', - // ignore: deprecated_member_use_from_same_package - mechanism: sentryException.mechanism?.copyWith(source: 'exceptions[2]'), - ); - exceptionGroupNested.addException(typeError); - - final sut = fixture.getSut(groupExceptions: true); - var event = SentryEvent(exceptions: [exceptionGroupNested]); - event = sut.apply(event, Hint())!; - final flattened = event.exceptions ?? []; - - expect(flattened.length, 7); - - // { - // "exception": { - // "values": [ - // { - // "type": "TypeError", - // "value": "int", - // "mechanism": { - // "type": "chained", - // "source": "exceptions[2]", - // "exception_id": 6, - // "parent_id": 0 - // } - // }, - // { - // "type": "ModuleNotFoundError", - // "value": "another_module", - // "mechanism": { - // "type": "chained", - // "source": "exceptions[1]", - // "exception_id": 5, - // "parent_id": 3 - // } - // }, - // { - // "type": "ImportError", - // "value": "no_such_module", - // "mechanism": { - // "type": "chained", - // "source": "exceptions[0]", - // "exception_id": 4, - // "parent_id": 3 - // } - // }, - // { - // "type": "ExceptionGroup", - // "value": "imports", - // "mechanism": { - // "type": "chained", - // "source": "exceptions[1]", - // "is_exception_group": true, - // "exception_id": 3, - // "parent_id": 0 - // } - // }, - // { - // "type": "ValueError", - // "value": "654", - // "mechanism": { - // "type": "chained", - // "source": "exceptions[0]", - // "exception_id": 2, - // "parent_id": 0 - // } - // }, - // { - // "type": "RuntimeError", - // "value": "something", - // "mechanism": { - // "type": "chained", - // "source": "__context__", - // "exception_id": 1, - // "parent_id": 0 - // } - // }, - // { - // "type": "ExceptionGroup", - // "value": "nested", - // "mechanism": { - // "type": "exceptionhook", - // "handled": false, - // "is_exception_group": true, - // "exception_id": 0 - // } - // }, - // ] - // } - // } - - expect(flattened[0].value, 'TypeError'); - expect(flattened[0].mechanism?.source, 'exceptions[2]'); - expect(flattened[0].mechanism?.exceptionId, 6); - expect(flattened[0].mechanism?.parentId, 0); - expect(flattened[0].mechanism?.type, 'chained'); - - expect(flattened[1].value, 'ModuleNotFoundError'); - expect(flattened[1].mechanism?.source, 'exceptions[1]'); - expect(flattened[1].mechanism?.exceptionId, 5); - expect(flattened[1].mechanism?.parentId, 3); - expect(flattened[1].mechanism?.type, 'chained'); - - expect(flattened[2].value, 'ImportError'); - expect(flattened[2].mechanism?.source, 'exceptions[0]'); - expect(flattened[2].mechanism?.exceptionId, 4); - expect(flattened[2].mechanism?.parentId, 3); - expect(flattened[2].mechanism?.type, 'chained'); - - expect(flattened[3].value, 'ExceptionGroup'); - expect(flattened[3].mechanism?.source, 'exceptions[1]'); - expect(flattened[3].mechanism?.isExceptionGroup, true); - expect(flattened[3].mechanism?.exceptionId, 3); - expect(flattened[3].mechanism?.parentId, 0); - expect(flattened[3].mechanism?.type, 'chained'); - - expect(flattened[4].value, 'ValueError'); - expect(flattened[4].mechanism?.source, 'exceptions[0]'); - expect(flattened[4].mechanism?.exceptionId, 2); - expect(flattened[4].mechanism?.parentId, 0); - expect(flattened[4].mechanism?.type, 'chained'); - - expect(flattened[5].value, 'RuntimeError'); - expect(flattened[5].mechanism?.exceptionId, 1); - expect(flattened[5].mechanism?.parentId, 0); - expect(flattened[5].mechanism?.type, 'chained'); - - expect(flattened[6].value, 'ExceptionGroup'); - expect(flattened[6].mechanism?.isExceptionGroup, true); - expect(flattened[6].mechanism?.exceptionId, 0); - expect(flattened[6].mechanism?.parentId, isNull); - expect( - flattened[6].mechanism?.type, exceptionGroupNested.mechanism?.type); - }); - }); -} - -class Fixture { - final options = defaultTestOptions(); - - ExceptionGroupEventProcessor getSut({required bool groupExceptions}) { - options.groupExceptions = groupExceptions; - return ExceptionGroupEventProcessor(options); - } -} - -class ExceptionA { - ExceptionA(this.other); - final ExceptionB? other; -} - -class ExceptionB { - ExceptionB(this.anotherOther); - final ExceptionC? anotherOther; -} - -class ExceptionC { - // I am empty inside -} diff --git a/dart/test/event_processor/exception/io_exception_event_processor_test.dart b/dart/test/event_processor/exception/io_exception_event_processor_test.dart deleted file mode 100644 index 109b07b8eb..0000000000 --- a/dart/test/event_processor/exception/io_exception_event_processor_test.dart +++ /dev/null @@ -1,132 +0,0 @@ -@TestOn('vm') -library; - -import 'dart:io'; - -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/event_processor/exception/io_exception_event_processor.dart'; -import 'package:test/test.dart'; -import 'package:sentry/src/sentry_exception_factory.dart'; - -import '../../test_utils.dart'; - -void main() { - group(IoExceptionEventProcessor, () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('adds $SentryRequest for $HttpException with uris', () { - final enricher = fixture.getSut(); - final event = enricher.apply( - SentryEvent( - throwable: HttpException( - '', - uri: Uri.parse('https://example.org/foo/bar?foo=bar'), - ), - ), - Hint(), - ); - - expect(event?.request, isNotNull); - expect(event?.request?.url, 'https://example.org/foo/bar'); - expect(event?.request?.queryString, 'foo=bar'); - }); - - test('no $SentryRequest for $HttpException without uris', () { - final enricher = fixture.getSut(); - final event = enricher.apply( - SentryEvent( - throwable: HttpException(''), - ), - Hint(), - ); - - expect(event?.request, isNull); - }); - - test('adds $SentryRequest for $SocketException with addresses', () { - final enricher = fixture.getSut(); - final throwable = SocketException( - 'Exception while connecting', - osError: OSError('Connection reset by peer', 54), - port: 12345, - address: InternetAddress( - '127.0.0.1', - type: InternetAddressType.IPv4, - ), - ); - final sentryException = - fixture.exceptionFactory.getSentryException(throwable); - - final event = enricher.apply( - SentryEvent( - throwable: throwable, - exceptions: [sentryException], - ), - Hint(), - ); - - expect(event?.request, isNotNull); - expect(event?.request?.url, '127.0.0.1'); - - final rootException = event?.exceptions?.first; - expect(rootException, sentryException); - - final childException = rootException?.exceptions?.first; - expect(childException?.type, 'OSError'); - expect(childException?.value, - 'OS Error: Connection reset by peer, errno = 54'); - expect(childException?.mechanism?.type, 'OSError'); - expect(childException?.mechanism?.meta['errno']['number'], 54); - expect(childException?.mechanism?.source, 'osError'); - }); - - test('adds OSError SentryException for $FileSystemException', () { - final enricher = fixture.getSut(); - final throwable = FileSystemException( - 'message', - 'path', - OSError('Oh no :(', 42), - ); - final sentryException = - fixture.exceptionFactory.getSentryException(throwable); - - final event = enricher.apply( - SentryEvent( - throwable: throwable, - exceptions: [sentryException], - ), - Hint(), - ); - - final rootException = event?.exceptions?.first; - expect(rootException, sentryException); - - final childException = rootException?.exceptions?.firstOrNull; - // Due to the test setup, there's no SentryException for the FileSystemException. - // And thus only one entry for the added OSError - expect(childException?.type, 'OSError'); - expect( - childException?.value, - 'OS Error: Oh no :(, errno = 42', - ); - expect(childException?.mechanism?.type, 'OSError'); - expect(childException?.mechanism?.meta['errno']['number'], 42); - expect(childException?.mechanism?.source, 'osError'); - }); - }); -} - -class Fixture { - final SentryOptions options = defaultTestOptions(); - - // ignore: invalid_use_of_internal_member - SentryExceptionFactory get exceptionFactory => options.exceptionFactory; - - IoExceptionEventProcessor getSut() { - return IoExceptionEventProcessor(SentryOptions.empty()); - } -} diff --git a/dart/test/example_web_compile_test.dart b/dart/test/example_web_compile_test.dart deleted file mode 100644 index 5f3a422580..0000000000 --- a/dart/test/example_web_compile_test.dart +++ /dev/null @@ -1,91 +0,0 @@ -@TestOn('vm') -library; - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:test/test.dart'; - -// Tests for the following issue -// https://github.com/getsentry/sentry-dart/issues/1893 -void main() { - final exampleAppDir = 'example_web'; - final exampleAppWorkingDir = - '${Directory.current.path}${Platform.pathSeparator}$exampleAppDir'; - group('Compile $exampleAppDir', () { - test( - 'dart pub get and compilation should run successfully', - () async { - final result = await _runProcess('dart pub get', - workingDirectory: exampleAppWorkingDir); - expect(result.exitCode, 0, - reason: 'Could run `dart pub get` for $exampleAppDir. ' - 'Likely caused by outdated dependencies'); - // running this test locally require clean working directory - final cleanResult = await _runProcess('dart run build_runner clean', - workingDirectory: exampleAppWorkingDir); - expect(cleanResult.exitCode, 0); - final compileResult = await _runProcess( - 'dart run build_runner build -r web -o build --delete-conflicting-outputs', - workingDirectory: exampleAppWorkingDir); - expect(compileResult.exitCode, 0, - reason: 'Could not compile $exampleAppDir project'); - expect( - compileResult.stdout, - isNot(contains( - 'Skipping compiling sentry_dart_web_example|web/main.dart')), - reason: - 'Could not compile main.dart, likely because of dart:io import.'); - expect( - compileResult.stdout, - anyOf( - contains('Built with build_runner'), - contains('build_web_compilers:entrypoint'), - ), - ); - }, - timeout: Timeout(const Duration(minutes: 1)), // double of detault timeout - ); - }); -} - -/// Runs [command] with command's stdout and stderr being forwrarded to -/// -/// test runner's respective streams. It buffers stdout and returns it. -/// -/// Returns [_CommandResult] with exitCode and stdout as a single sting -Future<_CommandResult> _runProcess(String command, - {String workingDirectory = '.'}) async { - final parts = command.split(' '); - assert(parts.isNotEmpty); - final cmd = parts[0]; - final args = parts.skip(1).toList(); - final process = - await Process.start(cmd, args, workingDirectory: workingDirectory); - // forward standard streams - unawaited(stderr.addStream(process.stderr)); - final buffer = []; - final stdoutCompleter = Completer.sync(); - process.stdout.listen( - (units) { - buffer.addAll(units); - stdout.add(units); - }, - cancelOnError: true, - onDone: () { - stdoutCompleter.complete(); - }, - ); - await stdoutCompleter.future; - final processOut = utf8.decode(buffer); - int exitCode = await process.exitCode; - return _CommandResult(exitCode: exitCode, stdout: processOut); -} - -class _CommandResult { - final int exitCode; - final String stdout; - - const _CommandResult({required this.exitCode, required this.stdout}); -} diff --git a/dart/test/exception_identifier_test.dart b/dart/test/exception_identifier_test.dart deleted file mode 100644 index 382a90b5d8..0000000000 --- a/dart/test/exception_identifier_test.dart +++ /dev/null @@ -1,186 +0,0 @@ -import 'package:mockito/mockito.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/dart_exception_type_identifier.dart'; -import 'package:sentry/src/sentry_exception_factory.dart'; -import 'package:test/test.dart'; - -import 'mocks.mocks.dart'; -import 'mocks/mock_transport.dart'; -import 'sentry_client_test.dart'; -import 'test_utils.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - group('ExceptionTypeIdentifiers', () { - test('should be processed based on order in the list', () { - fixture.options - .prependExceptionTypeIdentifier(DartExceptionTypeIdentifier()); - fixture.options - .prependExceptionTypeIdentifier(ObfuscatedExceptionIdentifier()); - - final factory = SentryExceptionFactory(fixture.options); - final sentryException = factory.getSentryException(ObfuscatedException()); - - expect(sentryException.type, equals('ObfuscatedException')); - }); - - test('should return null if exception is not identified', () { - final identifier = DartExceptionTypeIdentifier(); - expect(identifier.identifyType(ObfuscatedException()), isNull); - }); - }); - - group('SentryExceptionFactory', () { - test('should process identifiers based on order in the list', () { - fixture.options - .prependExceptionTypeIdentifier(DartExceptionTypeIdentifier()); - fixture.options - .prependExceptionTypeIdentifier(ObfuscatedExceptionIdentifier()); - - final factory = SentryExceptionFactory(fixture.options); - final sentryException = factory.getSentryException(ObfuscatedException()); - - expect(sentryException.type, equals('ObfuscatedException')); - }); - - test('should use runtime type when identification is disabled', () { - fixture.options.enableExceptionTypeIdentification = false; - fixture.options - .prependExceptionTypeIdentifier(ObfuscatedExceptionIdentifier()); - - final factory = SentryExceptionFactory(fixture.options); - final sentryException = factory.getSentryException(ObfuscatedException()); - - expect(sentryException.type, equals('PlaceHolderException')); - }); - }); - - group('CachingExceptionTypeIdentifier', () { - late MockExceptionTypeIdentifier mockIdentifier; - late CachingExceptionTypeIdentifier cachingIdentifier; - - setUp(() { - mockIdentifier = MockExceptionTypeIdentifier(); - cachingIdentifier = CachingExceptionTypeIdentifier(mockIdentifier); - }); - - test('should return cached result for known types', () { - final exception = Exception('Test'); - when(mockIdentifier.identifyType(exception)).thenReturn('TestException'); - - expect( - cachingIdentifier.identifyType(exception), equals('TestException')); - expect( - cachingIdentifier.identifyType(exception), equals('TestException')); - expect( - cachingIdentifier.identifyType(exception), equals('TestException')); - - verify(mockIdentifier.identifyType(exception)).called(1); - }); - - test('should not cache unknown types', () { - final exception = ObfuscatedException(); - - when(mockIdentifier.identifyType(exception)).thenReturn(null); - - expect(cachingIdentifier.identifyType(exception), isNull); - expect(cachingIdentifier.identifyType(exception), isNull); - expect(cachingIdentifier.identifyType(exception), isNull); - - verify(mockIdentifier.identifyType(exception)).called(3); - }); - - test('should return null for unknown exception type', () { - final exception = Exception('Unknown'); - when(mockIdentifier.identifyType(exception)).thenReturn(null); - - expect(cachingIdentifier.identifyType(exception), isNull); - }); - - test('should handle different exception types separately', () { - final exception1 = Exception('Test1'); - final exception2 = FormatException('Test2'); - - when(mockIdentifier.identifyType(exception1)).thenReturn('Exception'); - when(mockIdentifier.identifyType(exception2)) - .thenReturn('FormatException'); - - expect(cachingIdentifier.identifyType(exception1), equals('Exception')); - expect(cachingIdentifier.identifyType(exception2), - equals('FormatException')); - - // Call again to test caching - expect(cachingIdentifier.identifyType(exception1), equals('Exception')); - expect(cachingIdentifier.identifyType(exception2), - equals('FormatException')); - - verify(mockIdentifier.identifyType(exception1)).called(1); - verify(mockIdentifier.identifyType(exception2)).called(1); - }); - }); - - group('Integration test', () { - setUp(() { - fixture.options.transport = fixture.mockTransport; - }); - - test( - 'should capture CustomException as exception type with custom identifier', - () async { - fixture.options - .prependExceptionTypeIdentifier(ObfuscatedExceptionIdentifier()); - - final client = SentryClient(fixture.options); - - await client.captureException(ObfuscatedException()); - - final transport = fixture.mockTransport; - final capturedEnvelope = transport.envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect( - capturedEvent.exceptions!.first.type, equals('ObfuscatedException')); - }); - - test( - 'should capture PlaceHolderException as exception type without custom identifier', - () async { - final client = SentryClient(fixture.options); - - await client.captureException(ObfuscatedException()); - - final transport = fixture.mockTransport; - final capturedEnvelope = transport.envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect( - capturedEvent.exceptions!.first.type, equals('PlaceHolderException')); - }); - }); -} - -class Fixture { - final mockTransport = MockTransport(); - SentryOptions options = defaultTestOptions(); -} - -// We use this PlaceHolder exception to mimic an obfuscated runtimeType -class PlaceHolderException implements Exception {} - -class ObfuscatedException implements Exception { - @override - Type get runtimeType => PlaceHolderException; -} - -class ObfuscatedExceptionIdentifier implements ExceptionTypeIdentifier { - @override - String? identifyType(dynamic throwable) { - if (throwable is ObfuscatedException) return 'ObfuscatedException'; - return null; - } -} diff --git a/dart/test/feature_flags_integration_test.dart b/dart/test/feature_flags_integration_test.dart deleted file mode 100644 index 94d7cfb430..0000000000 --- a/dart/test/feature_flags_integration_test.dart +++ /dev/null @@ -1,117 +0,0 @@ -library; - -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; -import 'package:sentry/src/feature_flags_integration.dart'; - -import 'test_utils.dart'; -import 'mocks/mock_hub.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('adds itself to sdk.integrations', () { - final sut = fixture.getSut(); - - sut.call(fixture.hub, fixture.options); - - expect(fixture.options.sdk.integrations.contains('FeatureFlagsIntegration'), - isTrue); - }); - - test('adds feature flag to scope', () async { - final sut = fixture.getSut(); - - sut.call(fixture.hub, fixture.options); - - await sut.addFeatureFlag('foo', true); - - expect(fixture.hub.scope.contexts[SentryFeatureFlags.type], isNotNull); - expect( - fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.first.flag, - equals('foo')); - expect( - fixture - .hub.scope.contexts[SentryFeatureFlags.type]?.values.first.result, - equals(true)); - }); - - test('replaces existing feature flag', () async { - final sut = fixture.getSut(); - - sut.call(fixture.hub, fixture.options); - - await sut.addFeatureFlag('foo', true); - await sut.addFeatureFlag('foo', false); - - expect(fixture.hub.scope.contexts[SentryFeatureFlags.type], isNotNull); - expect( - fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.first.flag, - equals('foo')); - expect( - fixture - .hub.scope.contexts[SentryFeatureFlags.type]?.values.first.result, - equals(false)); - }); - - test('removes oldest feature flag when there are more than 100', () async { - final sut = fixture.getSut(); - - sut.call(fixture.hub, fixture.options); - - for (var i = 0; i < 100; i++) { - await sut.addFeatureFlag('foo_$i', i % 2 == 0 ? true : false); - } - - expect(fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.length, - equals(100)); - - expect( - fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.first.flag, - equals('foo_0')); - expect( - fixture - .hub.scope.contexts[SentryFeatureFlags.type]?.values.first.result, - equals(true)); - - expect( - fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.last.flag, - equals('foo_99')); - expect( - fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.last.result, - equals(false)); - - await sut.addFeatureFlag('foo_100', true); - - expect(fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.length, - equals(100)); - - expect( - fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.first.flag, - equals('foo_1')); - expect( - fixture - .hub.scope.contexts[SentryFeatureFlags.type]?.values.first.result, - equals(false)); - - expect( - fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.last.flag, - equals('foo_100')); - expect( - fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.last.result, - equals(true)); - }); -} - -class Fixture { - final hub = MockHub(); - final options = defaultTestOptions(); - - FeatureFlagsIntegration getSut() { - return FeatureFlagsIntegration(); - } -} diff --git a/dart/test/hint_test.dart b/dart/test/hint_test.dart deleted file mode 100644 index 04c09a28a0..0000000000 --- a/dart/test/hint_test.dart +++ /dev/null @@ -1,108 +0,0 @@ -import 'package:sentry/src/hint.dart'; -import 'package:sentry/src/sentry_attachment/sentry_attachment.dart'; -import 'package:test/test.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('Hint init with map', () { - final hint = Hint.withMap({'fixture-key': 'fixture-value'}); - expect("fixture-value", hint.get("fixture-key")); - }); - - test('Hint set value is returned with get', () { - final hint = Hint(); - hint.set("hint1", "some string"); - - expect("some string", hint.get("hint1")); - }); - - test('Hint get returns null if not contained', () { - final hint = Hint(); - expect(hint.get("hint-does-not-exist"), null); - }); - - test('Hint set multiple times only keeps latest value', () { - final hint = Hint(); - - hint.set("hint1", "some string"); - hint.set("hint1", "a different string"); - - expect("a different string", hint.get("hint1")); - }); - - test('Hint removed value is not returned by get', () { - final hint = Hint(); - - hint.set("hint1", "some string"); - expect("some string", hint.get("hint1")); - - hint.remove("hint1"); - expect(hint.get("hint1"), null); - }); - - test('Hint remove leaves other values', () { - final hint = Hint(); - - hint.set("hint1", "some string"); - expect("some string", hint.get("hint1")); - hint.set("hint2", "another string"); - - hint.remove("hint1"); - expect(hint.get("hint1"), null); - expect("another string", hint.get("hint2")); - }); - - test('Hint clear removes all values', () { - final hint = Hint(); - - hint.set("hint1", "some string"); - hint.set("hint2", "another string"); - - hint.clear(); - expect(hint.get("hint1"), null); - expect(hint.get("hint2"), null); - }); - - test('clear does not remove attachments, screenshot & viewHierarchy', () { - final attachment = SentryAttachment.fromIntList([], "fixture-fileName"); - - final sut = fixture.givenSut(); - sut.attachments.add(attachment); - sut.screenshot = attachment; - sut.viewHierarchy = attachment; - - sut.clear(); - - expect(sut.attachments.contains(attachment), true); - expect(sut.screenshot, attachment); - expect(sut.viewHierarchy, attachment); - }); - - test('Hint init with map null fallback', () { - final hint = Hint.withMap({'fixture-key': null}); - expect("null", hint.get("fixture-key")); - }); - - test('Hint addAll with map null fallback', () { - final hint = Hint(); - hint.addAll({'fixture-key': null}); - expect("null", hint.get("fixture-key")); - }); - - test('Hint set with null value fallback', () { - final hint = Hint(); - hint.set("fixture-key", null); - expect("null", hint.get("fixture-key")); - }); -} - -class Fixture { - Hint givenSut() { - return Hint(); - } -} diff --git a/dart/test/http_client/breadcrumb_client_test.dart b/dart/test/http_client/breadcrumb_client_test.dart deleted file mode 100644 index 73d60ef035..0000000000 --- a/dart/test/http_client/breadcrumb_client_test.dart +++ /dev/null @@ -1,301 +0,0 @@ -import 'dart:io'; - -import 'package:http/http.dart'; -import 'package:http/testing.dart'; -import 'package:mockito/mockito.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/http_client/breadcrumb_client.dart'; -import 'package:test/test.dart'; - -import '../mocks/mock_hub.dart'; - -final requestUri = Uri.parse('https://example.com/path?foo=bar#baz'); - -void main() { - group(BreadcrumbClient, () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('GET: happy path', () async { - final sut = - fixture.getSut(fixture.getClient(statusCode: 200, reason: 'OK')); - - final response = await sut.get(requestUri); - expect(response.statusCode, 200); - - expect(fixture.hub.addBreadcrumbCalls.length, 1); - final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; - - expect(breadcrumb.type, 'http'); - expect(breadcrumb.data?['url'], 'https://example.com/path'); - expect(breadcrumb.data?['method'], 'GET'); - expect(breadcrumb.data?['http.query'], 'foo=bar'); - expect(breadcrumb.data?['http.fragment'], 'baz'); - expect(breadcrumb.data?['status_code'], 200); - expect(breadcrumb.data?['reason'], 'OK'); - expect(breadcrumb.data?['duration'], isNotNull); - expect(breadcrumb.data?['request_body_size'], isNotNull); - expect(breadcrumb.data?['response_body_size'], isNotNull); - expect(breadcrumb.level, SentryLevel.info); - }); - - test('GET: happy path for 404', () async { - final sut = fixture - .getSut(fixture.getClient(statusCode: 404, reason: 'NOT FOUND')); - - final response = await sut.get(requestUri); - - expect(response.statusCode, 404); - - expect(fixture.hub.addBreadcrumbCalls.length, 1); - final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; - - expect(breadcrumb.type, 'http'); - expect(breadcrumb.data?['url'], 'https://example.com/path'); - expect(breadcrumb.data?['method'], 'GET'); - expect(breadcrumb.data?['http.query'], 'foo=bar'); - expect(breadcrumb.data?['http.fragment'], 'baz'); - expect(breadcrumb.data?['status_code'], 404); - expect(breadcrumb.data?['reason'], 'NOT FOUND'); - expect(breadcrumb.data?['duration'], isNotNull); - expect(breadcrumb.level, SentryLevel.warning); - }); - - test('POST: happy path', () async { - final sut = fixture.getSut(fixture.getClient(statusCode: 200)); - - final response = await sut.post(requestUri); - expect(response.statusCode, 200); - - expect(fixture.hub.addBreadcrumbCalls.length, 1); - final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; - - expect(breadcrumb.type, 'http'); - expect(breadcrumb.data?['url'], 'https://example.com/path'); - expect(breadcrumb.data?['method'], 'POST'); - expect(breadcrumb.data?['http.query'], 'foo=bar'); - expect(breadcrumb.data?['http.fragment'], 'baz'); - expect(breadcrumb.data?['status_code'], 200); - expect(breadcrumb.data?['duration'], isNotNull); - expect(breadcrumb.level, SentryLevel.info); - }); - - test('PUT: happy path', () async { - final sut = fixture.getSut(fixture.getClient(statusCode: 200)); - - final response = await sut.put(requestUri); - expect(response.statusCode, 200); - - expect(fixture.hub.addBreadcrumbCalls.length, 1); - final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; - - expect(breadcrumb.type, 'http'); - expect(breadcrumb.data?['url'], 'https://example.com/path'); - expect(breadcrumb.data?['method'], 'PUT'); - expect(breadcrumb.data?['http.query'], 'foo=bar'); - expect(breadcrumb.data?['http.fragment'], 'baz'); - expect(breadcrumb.data?['status_code'], 200); - expect(breadcrumb.data?['duration'], isNotNull); - expect(breadcrumb.level, SentryLevel.info); - }); - - test('DELETE: happy path', () async { - final sut = fixture.getSut(fixture.getClient(statusCode: 200)); - - final response = await sut.delete(requestUri); - expect(response.statusCode, 200); - - expect(fixture.hub.addBreadcrumbCalls.length, 1); - final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; - - expect(breadcrumb.type, 'http'); - expect(breadcrumb.data?['url'], 'https://example.com/path'); - expect(breadcrumb.data?['method'], 'DELETE'); - expect(breadcrumb.data?['http.query'], 'foo=bar'); - expect(breadcrumb.data?['http.fragment'], 'baz'); - expect(breadcrumb.data?['status_code'], 200); - expect(breadcrumb.data?['duration'], isNotNull); - expect(breadcrumb.level, SentryLevel.info); - }); - - test('server error response (500)', () async { - final sut = fixture.getSut( - fixture.getClient(statusCode: 500, reason: 'INTERNAL SERVER ERROR')); - - final response = await sut.get(requestUri); - - expect(response.statusCode, 500); - - expect(fixture.hub.addBreadcrumbCalls.length, 1); - final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; - - expect(breadcrumb.type, 'http'); - expect(breadcrumb.data?['url'], 'https://example.com/path'); - expect(breadcrumb.data?['method'], 'GET'); - expect(breadcrumb.data?['http.query'], 'foo=bar'); - expect(breadcrumb.data?['http.fragment'], 'baz'); - expect(breadcrumb.data?['status_code'], 500); - expect(breadcrumb.data?['reason'], 'INTERNAL SERVER ERROR'); - expect(breadcrumb.data?['duration'], isNotNull); - expect(breadcrumb.level, SentryLevel.error); - }); - - test('server redirect (3xx)', () async { - final sut = fixture.getSut( - fixture.getClient(statusCode: 308, reason: 'PERMANENT REDIRECT')); - - final response = await sut.get(requestUri); - - expect(response.statusCode, 308); - - expect(fixture.hub.addBreadcrumbCalls.length, 1); - final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; - - expect(breadcrumb.type, 'http'); - expect(breadcrumb.data?['url'], 'https://example.com/path'); - expect(breadcrumb.data?['method'], 'GET'); - expect(breadcrumb.data?['http.query'], 'foo=bar'); - expect(breadcrumb.data?['http.fragment'], 'baz'); - expect(breadcrumb.data?['status_code'], 308); - expect(breadcrumb.data?['reason'], 'PERMANENT REDIRECT'); - expect(breadcrumb.data?['duration'], isNotNull); - expect(breadcrumb.level, SentryLevel.info); - }); - - test('invalid status (>= 6xx)', () async { - final sut = fixture.getSut( - fixture.getClient(statusCode: 600, reason: 'UNKNOWN STATUS CODE')); - - final response = await sut.get(requestUri); - - expect(response.statusCode, 600); - - expect(fixture.hub.addBreadcrumbCalls.length, 1); - final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; - - expect(breadcrumb.type, 'http'); - expect(breadcrumb.data?['url'], 'https://example.com/path'); - expect(breadcrumb.data?['method'], 'GET'); - expect(breadcrumb.data?['http.query'], 'foo=bar'); - expect(breadcrumb.data?['http.fragment'], 'baz'); - expect(breadcrumb.data?['status_code'], 600); - expect(breadcrumb.data?['reason'], 'UNKNOWN STATUS CODE'); - expect(breadcrumb.data?['duration'], isNotNull); - expect(breadcrumb.level, SentryLevel.info); - }); - - /// Tests, that in case an exception gets thrown, that - /// no exception gets reported by Sentry, in case the user wants to - /// handle the exception - test('no captureException for ClientException', () async { - final sut = fixture.getSut(MockClient((request) async { - expect(request.url, requestUri); - throw ClientException('test', requestUri); - })); - - try { - await sut.get(requestUri); - fail('Method did not throw'); - } on ClientException catch (e) { - expect(e.message, 'test'); - expect(e.uri, requestUri); - } - - expect(fixture.hub.captureExceptionCalls.length, 0); - }); - - /// SocketException are only a thing on dart:io platforms. - /// otherwise this is equal to the test above - test('no captureException for SocketException', () async { - final sut = fixture.getSut(MockClient((request) async { - expect(request.url, requestUri); - throw SocketException('test'); - })); - - try { - await sut.get(requestUri); - fail('Method did not throw'); - } on SocketException catch (e) { - expect(e.message, 'test'); - } - - expect(fixture.hub.captureExceptionCalls.length, 0); - }); - - test('breadcrumb gets added when an exception gets thrown', () async { - final sut = fixture.getSut(MockClient((request) async { - expect(request.url, requestUri); - throw Exception('foo bar'); - })); - - try { - await sut.get(requestUri); - fail('Method did not throw'); - } on Exception catch (_) {} - - expect(fixture.hub.addBreadcrumbCalls.length, 1); - - final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; - - expect(breadcrumb.type, 'http'); - expect(breadcrumb.data?['url'], 'https://example.com/path'); - expect(breadcrumb.data?['method'], 'GET'); - expect(breadcrumb.data?['http.query'], 'foo=bar'); - expect(breadcrumb.data?['http.fragment'], 'baz'); - expect(breadcrumb.level, SentryLevel.error); - expect(breadcrumb.data?['duration'], isNotNull); - }); - - test('close does get called for user defined client', () async { - final mockHub = MockHub(); - - final mockClient = CloseableMockClient(); - - final client = BreadcrumbClient(client: mockClient, hub: mockHub); - client.close(); - - expect(mockHub.addBreadcrumbCalls.length, 0); - expect(mockHub.captureExceptionCalls.length, 0); - verify(mockClient.close()); - }); - - test('Breadcrumb has correct duration', () async { - final sut = fixture.getSut(MockClient((request) async { - expect(request.url, requestUri); - await Future.delayed(Duration(seconds: 1)); - return Response('', 200, reasonPhrase: 'OK'); - })); - - final response = await sut.get(requestUri); - expect(response.statusCode, 200); - - expect(fixture.hub.addBreadcrumbCalls.length, 1); - final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; - - var durationString = breadcrumb.data!['duration']! as String; - // we don't check for anything below a second - expect(durationString.startsWith('0:00:01'), true); - }); - }); -} - -class CloseableMockClient extends Mock implements BaseClient {} - -class Fixture { - BreadcrumbClient getSut([MockClient? client]) { - final mc = client ?? getClient(); - return BreadcrumbClient(client: mc, hub: hub); - } - - late MockHub hub = MockHub(); - - MockClient getClient({int statusCode = 200, String? reason}) { - return MockClient((request) async { - expect(request.url, requestUri); - return Response('', statusCode, reasonPhrase: reason); - }); - } -} diff --git a/dart/test/http_client/failed_request_client_test.dart b/dart/test/http_client/failed_request_client_test.dart deleted file mode 100644 index b48d7e5d4a..0000000000 --- a/dart/test/http_client/failed_request_client_test.dart +++ /dev/null @@ -1,405 +0,0 @@ -import 'package:http/http.dart'; -import 'package:http/testing.dart'; -import 'package:mockito/mockito.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/http_client/failed_request_client.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; -import '../mocks/mock_hub.dart'; -import '../mocks/mock_transport.dart'; -import '../test_utils.dart'; - -final requestUri = Uri.parse('https://example.com?foo=bar#myFragment'); - -void main() { - group(FailedRequestClient, () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('no captured events when everything goes well', () async { - final sut = fixture.getSut( - client: fixture.getClient(statusCode: 200), - ); - - final response = await sut.get(requestUri); - expect(response.statusCode, 200); - - expect(fixture.transport.calls, 0); - }); - - test('exception gets reported if client throws', () async { - fixture._hub.options.captureFailedRequests = true; - fixture._hub.options.sendDefaultPii = true; - - final sut = fixture.getSut( - client: createThrowingClient(), - ); - - await expectLater( - () async => await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}), - throwsException, - ); - - expect(fixture.transport.calls, 1); - - final eventCall = fixture.transport.events.first; - final exception = eventCall.exceptions?.first; - final mechanism = exception?.mechanism; - - expect(exception?.stackTrace, isNotNull); - expect(exception?.stackTrace!.snapshot, isNull); - expect(mechanism?.type, 'SentryHttpClient'); - - final request = eventCall.request; - expect(request, isNotNull); - expect(request?.method, 'GET'); - expect(request?.url, 'https://example.com'); - expect(request?.queryString, 'foo=bar'); - expect(request?.fragment, 'myFragment'); - expect(request?.cookies, isNull); - expect(request?.headers, {}); - - // Response is not captured in case of exception - expect(eventCall.contexts.response, isNull); - }); - - test( - 'exception does not gets reported if client throws but override disables capture', - () async { - fixture._hub.options.captureFailedRequests = true; - fixture._hub.options.sendDefaultPii = true; - - final sut = fixture.getSut( - client: createThrowingClient(), - captureFailedRequests: false, - ); - - await expectLater( - () async => await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}), - throwsException, - ); - - expect(fixture.transport.calls, 0); - }); - - test('event not reported if disabled', () async { - fixture._hub.options.captureFailedRequests = false; - - final sut = fixture.getSut( - client: createThrowingClient(), - ); - - await expectLater( - () async => await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}), - throwsException, - ); - - expect(fixture.transport.calls, 0); - }); - - test('event reported if disabled but overridden', () async { - fixture._hub.options.captureFailedRequests = false; - - final sut = fixture.getSut( - client: createThrowingClient(), - captureFailedRequests: true, - ); - - await expectLater( - () async => await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}), - throwsException, - ); - - expect(fixture.transport.calls, 1); - }); - - test('event not reported if not within the targets', () async { - fixture._hub.options.captureFailedRequests = true; - - final sut = fixture.getSut( - client: fixture.getClient(statusCode: 500), - failedRequestTargets: const ["myapi.com"]); - - final response = await sut.get(requestUri); - - expect(response.statusCode, 500); - expect(fixture.transport.calls, 0); - }); - - test('exception gets reported if bad status code occurs', () async { - fixture._hub.options.sendDefaultPii = true; - - final sut = fixture.getSut( - client: fixture.getClient( - statusCode: 404, - body: 'foo', - headers: {'lorem': 'ipsum', 'set-cookie': 'foo=bar'}), - failedRequestStatusCodes: [SentryStatusCode(404)], - ); - - await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}); - - expect(fixture.transport.calls, 1); - - final eventCall = fixture.transport.events.first; - final exception = eventCall.exceptions?.first; - final mechanism = exception?.mechanism; - - expect(mechanism?.type, 'SentryHttpClient'); - expect( - mechanism?.description, - 'HTTP Client Error with status code: 404', - ); - - expect(exception?.type, 'SentryHttpClientError'); - expect( - exception?.value, - 'Exception: HTTP Client Error with status code: 404', - ); - expect(exception?.stackTrace?.snapshot, true); - - final request = eventCall.request; - expect(request, isNotNull); - expect(request?.method, 'GET'); - expect(request?.url, 'https://example.com'); - expect(request?.queryString, 'foo=bar'); - expect(request?.fragment, 'myFragment'); - expect(request?.cookies, isNull); - expect(request?.headers, {}); - - final response = eventCall.contexts.response!; - expect(response.bodySize, 3); - expect(response.statusCode, 404); - expect(response.headers, - equals({'lorem': 'ipsum', 'set-cookie': 'foo=bar'})); - expect(response.cookies, equals('foo=bar')); - }); - - test( - 'just one report on status code reporting with failing requests enabled', - () async { - final sut = fixture.getSut( - client: fixture.getClient(statusCode: 404), - failedRequestStatusCodes: [SentryStatusCode(404)], - ); - - await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}); - - expect(fixture.transport.calls, 1); - }); - - test('close does get called for user defined client', () async { - final mockHub = MockHub(); - - final mockClient = CloseableMockClient(); - - final client = FailedRequestClient(client: mockClient, hub: mockHub); - client.close(); - - expect(mockHub.addBreadcrumbCalls.length, 0); - expect(mockHub.captureExceptionCalls.length, 0); - verify(mockClient.close()); - }); - - test('pii is not send on exception', () async { - fixture._hub.options.captureFailedRequests = true; - final sut = fixture.getSut( - client: createThrowingClient(), - ); - - await expectLater( - () async => await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}), - throwsException, - ); - - final event = fixture.transport.events.first; - expect(fixture.transport.calls, 1); - expect(event.request, isNotNull); - expect(event.request?.headers.isEmpty, true); - expect(event.request?.cookies, isNull); - expect(event.request?.data, isNull); - expect(event.contexts.response, isNull); - }); - - test('removes authorization headers', () async { - fixture._hub.options.captureFailedRequests = true; - final sut = fixture.getSut( - client: createThrowingClient(), - ); - - await expectLater( - () async => await sut.get(requestUri, - headers: {'authorization': 'foo', 'Authorization': 'foo'}), - throwsException, - ); - - final event = fixture.transport.events.first; - expect(fixture.transport.calls, 1); - expect(event.request, isNotNull); - expect(event.request?.headers.isEmpty, true); - }); - - test('pii is not send on invalid status code', () async { - final sut = fixture.getSut( - client: fixture.getClient(statusCode: 404), - failedRequestStatusCodes: [SentryStatusCode(404)], - ); - - await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}); - - final event = fixture.transport.events.first; - expect(fixture.transport.calls, 1); - expect(event.request, isNotNull); - expect(event.request?.headers.isEmpty, true); - expect(event.request?.cookies, isNull); - expect(event.request?.data, isNull); - expect(event.contexts.response, isNotNull); - expect(event.contexts.response?.headers.isEmpty, true); - }); - - test('request body is included according to $MaxRequestBodySize', () async { - final scenarios = [ - // // never - MaxBodySizeTestConfig(MaxRequestBodySize.never, 0, false), - MaxBodySizeTestConfig(MaxRequestBodySize.never, 4001, false), - MaxBodySizeTestConfig(MaxRequestBodySize.never, 10001, false), - // // always - MaxBodySizeTestConfig(MaxRequestBodySize.always, 0, true), - MaxBodySizeTestConfig(MaxRequestBodySize.always, 4001, true), - MaxBodySizeTestConfig(MaxRequestBodySize.always, 10001, true), - // // small - MaxBodySizeTestConfig(MaxRequestBodySize.small, 0, true), - MaxBodySizeTestConfig(MaxRequestBodySize.small, 4000, true), - MaxBodySizeTestConfig(MaxRequestBodySize.small, 4001, false), - // // medium - MaxBodySizeTestConfig(MaxRequestBodySize.medium, 0, true), - MaxBodySizeTestConfig(MaxRequestBodySize.medium, 4001, true), - MaxBodySizeTestConfig(MaxRequestBodySize.medium, 10000, true), - MaxBodySizeTestConfig(MaxRequestBodySize.medium, 10001, false), - ]; - - fixture._hub.options.captureFailedRequests = true; - fixture._hub.options.sendDefaultPii = true; - for (final scenario in scenarios) { - fixture._hub.options.maxRequestBodySize = scenario.maxBodySize; - fixture.transport.reset(); - - final sut = fixture.getSut( - client: createThrowingClient(), - ); - - final request = Request('GET', requestUri) - // This creates a a request of the specified size - ..bodyBytes = List.generate(scenario.contentLength, (index) => 0); - - await expectLater( - () async => await sut.send(request), - throwsException, - ); - - expect(fixture.transport.calls, 1); - - final eventCall = fixture.transport.events.first; - final capturedRequest = eventCall.request; - expect(capturedRequest, isNotNull); - expect(capturedRequest?.data, - scenario.shouldBeIncluded ? isNotNull : isNull); - } - }); - - test('request passed to hint', () async { - fixture._hub.options.captureFailedRequests = true; - - Request? failedRequest; - final client = MockClient( - (request) async { - failedRequest = request; - throw TestException(); - }, - ); - - final sut = fixture.getSut(client: client); - - Hint? eventHint; - fixture.options.addEventProcessor(FunctionEventProcessor((event, hint) { - eventHint = hint; - return event; - })); - - await expectLater( - () async => await sut.get(requestUri), - throwsException, - ); - - expect((eventHint?.get('request') as Request?)?.url, failedRequest?.url); - }); - }); -} - -MockClient createThrowingClient() { - return MockClient( - (request) async { - expect(request.url, requestUri); - throw TestException(); - }, - ); -} - -class CloseableMockClient extends Mock implements BaseClient {} - -class Fixture { - final options = defaultTestOptions(); - late Hub _hub; - final transport = MockTransport(); - Fixture() { - options.transport = transport; - _hub = Hub(options); - } - - FailedRequestClient getSut({ - MockClient? client, - List failedRequestStatusCodes = const [ - SentryStatusCode.defaultRange() - ], - List failedRequestTargets = const [".*"], - bool? captureFailedRequests, - }) { - final mc = client ?? getClient(); - return FailedRequestClient( - client: mc, - hub: _hub, - failedRequestStatusCodes: failedRequestStatusCodes, - failedRequestTargets: failedRequestTargets, - captureFailedRequests: captureFailedRequests); - } - - MockClient getClient( - {int statusCode = 200, - String body = '', - Map headers = const {}}) { - return MockClient((request) async { - expect(request.url, requestUri); - return Response(body, statusCode, headers: headers); - }); - } -} - -class TestException implements Exception {} - -class MaxBodySizeTestConfig { - MaxBodySizeTestConfig( - this.maxBodySize, - this.contentLength, - this.shouldBeIncluded, - ); - - final T maxBodySize; - final int contentLength; - final bool shouldBeIncluded; - - Matcher get matcher => shouldBeIncluded ? isNotNull : isNull; -} diff --git a/dart/test/http_client/io_client_provider_test.dart b/dart/test/http_client/io_client_provider_test.dart deleted file mode 100644 index 211c3e3900..0000000000 --- a/dart/test/http_client/io_client_provider_test.dart +++ /dev/null @@ -1,267 +0,0 @@ -@TestOn('vm') -library; - -import 'dart:io'; - -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/http_client/io_client_provider.dart'; -import 'package:test/test.dart'; - -import '../test_utils.dart'; - -void main() { - group('getClient', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('http proxy should call findProxyResult', () async { - fixture.options.proxy = SentryProxy( - type: SentryProxyType.http, - host: 'localhost', - port: 8080, - ); - - final sut = fixture.getSut(); - sut.getClient(fixture.options); - - expect(fixture.mockHttpClient.findProxyResult, - equals(fixture.options.proxy?.toPacString())); - }); - - test('direct proxy should call findProxyResult', () async { - fixture.options.proxy = SentryProxy(type: SentryProxyType.direct); - - final sut = fixture.getSut(); - sut.getClient(fixture.options); - - expect(fixture.mockHttpClient.findProxyResult, - equals(fixture.options.proxy?.toPacString())); - }); - - test('socks proxy should not call findProxyResult', () async { - fixture.options.proxy = SentryProxy( - type: SentryProxyType.socks, host: 'localhost', port: 8080); - - final sut = fixture.getSut(); - sut.getClient(fixture.options); - - expect(fixture.mockHttpClient.findProxyResult, isNull); - }); - - test('authenticated proxy http should call addProxyCredentials', () async { - fixture.options.proxy = SentryProxy( - type: SentryProxyType.http, - host: 'localhost', - port: 8080, - user: 'admin', - pass: '0000', - ); - - final sut = fixture.getSut(); - - sut.getClient(fixture.options); - - expect(fixture.mockHttpClient.addProxyCredentialsHost, - fixture.options.proxy?.host); - expect(fixture.mockHttpClient.addProxyCredentialsPort, - fixture.options.proxy?.port); - expect(fixture.mockHttpClient.addProxyCredentialsRealm, ''); - expect(fixture.mockUser, fixture.options.proxy?.user); - expect(fixture.mockPass, fixture.options.proxy?.pass); - expect(fixture.mockHttpClient.addProxyCredentialsBasic, isNotNull); - }); - }); -} - -class Fixture { - final options = defaultTestOptions(); - final mockHttpClient = MockHttpClient(); - - String? mockUser; - String? mockPass; - - IoClientProvider getSut() { - return IoClientProvider( - () { - return mockHttpClient; - }, - (user, pass) { - mockUser = user; - mockPass = pass; - return HttpClientBasicCredentials(user, pass); - }, - ); - } -} - -class MockHttpClient implements HttpClient { - @override - bool autoUncompress = false; - - @override - Duration? connectionTimeout; - - @override - Duration idleTimeout = Duration(seconds: 1); - - @override - int? maxConnectionsPerHost; - - @override - String? userAgent; - - @override - void addCredentials( - Uri url, String realm, HttpClientCredentials credentials) { - // TODO: implement addCredentials - } - - String? addProxyCredentialsHost; - int? addProxyCredentialsPort; - String? addProxyCredentialsRealm; - HttpClientBasicCredentials? addProxyCredentialsBasic; - - @override - void addProxyCredentials( - String host, int port, String realm, HttpClientCredentials credentials) { - addProxyCredentialsHost = host; - addProxyCredentialsPort = port; - addProxyCredentialsRealm = realm; - if (credentials is HttpClientBasicCredentials) { - addProxyCredentialsBasic = credentials; - } - } - - @override - set authenticate( - Future Function(Uri url, String scheme, String? realm)? f) { - // TODO: implement authenticate - } - - @override - set authenticateProxy( - Future Function( - String host, int port, String scheme, String? realm)? - f) { - // TODO: implement authenticateProxy - } - - @override - set badCertificateCallback( - bool Function(X509Certificate cert, String host, int port)? callback) { - // TODO: implement badCertificateCallback - } - - @override - void close({bool force = false}) { - // TODO: implement close - } - - @override - set connectionFactory( - Future> Function( - Uri url, String? proxyHost, int? proxyPort)? - f) { - // TODO: implement connectionFactory - } - - @override - Future delete(String host, int port, String path) { - // TODO: implement delete - throw UnimplementedError(); - } - - @override - Future deleteUrl(Uri url) { - // TODO: implement deleteUrl - throw UnimplementedError(); - } - - String? findProxyResult; - - @override - set findProxy(String Function(Uri url)? f) { - findProxyResult = f!(Uri(scheme: "http", host: "localhost", port: 8080)); - } - - @override - Future get(String host, int port, String path) { - // TODO: implement get - throw UnimplementedError(); - } - - @override - Future getUrl(Uri url) { - // TODO: implement getUrl - throw UnimplementedError(); - } - - @override - Future head(String host, int port, String path) { - // TODO: implement head - throw UnimplementedError(); - } - - @override - Future headUrl(Uri url) { - // TODO: implement headUrl - throw UnimplementedError(); - } - - @override - set keyLog(Function(String line)? callback) { - // TODO: implement keyLog - } - - @override - Future open( - String method, String host, int port, String path) { - // TODO: implement open - throw UnimplementedError(); - } - - @override - Future openUrl(String method, Uri url) { - // TODO: implement openUrl - throw UnimplementedError(); - } - - @override - Future patch(String host, int port, String path) { - // TODO: implement patch - throw UnimplementedError(); - } - - @override - Future patchUrl(Uri url) { - // TODO: implement patchUrl - throw UnimplementedError(); - } - - @override - Future post(String host, int port, String path) { - // TODO: implement post - throw UnimplementedError(); - } - - @override - Future postUrl(Uri url) { - // TODO: implement postUrl - throw UnimplementedError(); - } - - @override - Future put(String host, int port, String path) { - // TODO: implement put - throw UnimplementedError(); - } - - @override - Future putUrl(Uri url) { - // TODO: implement putUrl - throw UnimplementedError(); - } -} diff --git a/dart/test/http_client/sentry_http_client_test.dart b/dart/test/http_client/sentry_http_client_test.dart deleted file mode 100644 index f70c9fbcea..0000000000 --- a/dart/test/http_client/sentry_http_client_test.dart +++ /dev/null @@ -1,201 +0,0 @@ -import 'package:http/http.dart'; -import 'package:http/testing.dart'; -import 'package:mockito/mockito.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/http_client/failed_request_client.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:test/test.dart'; - -import '../mocks/mock_hub.dart'; -import '../mocks/mock_transport.dart'; -import '../test_utils.dart'; - -final requestUri = Uri.parse('https://example.com'); - -void main() { - group(SentryHttpClient, () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test( - 'no captured events & one captured breadcrumb when everything goes well', - () async { - final sut = fixture.getSut( - client: fixture.getClient(statusCode: 200, reason: 'OK'), - ); - - final response = await sut.get(requestUri); - expect(response.statusCode, 200); - - expect(fixture.mockHub.captureEventCalls.length, 0); - expect(fixture.mockHub.addBreadcrumbCalls.length, 1); - }); - - test('no captured event with default config', () async { - fixture.mockHub.options.captureFailedRequests = false; - - final sut = fixture.getSut( - client: createThrowingClient(), - ); - - await expectLater(() async => await sut.get(requestUri), throwsException); - - expect(fixture.mockHub.captureEventCalls.length, 0); - expect(fixture.mockHub.addBreadcrumbCalls.length, 1); - }); - - test('captured event with override', () async { - fixture.mockHub.options.captureFailedRequests = false; - - final sut = fixture.getSut( - client: createThrowingClient(), - captureFailedRequests: true, - ); - - await expectLater(() async => await sut.get(requestUri), throwsException); - - expect(fixture.mockHub.captureEventCalls.length, 1); - }); - - test('one captured event with when enabling $FailedRequestClient', - () async { - fixture.mockHub.options.captureFailedRequests = true; - fixture.mockHub.options.recordHttpBreadcrumbs = true; - final sut = fixture.getSut( - client: createThrowingClient(), - ); - - await expectLater(() async => await sut.get(requestUri), throwsException); - - expect(fixture.mockHub.captureEventCalls.length, 1); - // The event should not have breadcrumbs from the BreadcrumbClient - expect(fixture.mockHub.captureEventCalls.first.event.breadcrumbs, null); - // The breadcrumb for the request should still be added for every - // following event. - expect(fixture.mockHub.addBreadcrumbCalls.length, 1); - }); - - test( - 'no captured event with when enabling $FailedRequestClient with override', - () async { - fixture.mockHub.options.captureFailedRequests = true; - final sut = fixture.getSut( - client: createThrowingClient(), - captureFailedRequests: false, - ); - - await expectLater(() async => await sut.get(requestUri), throwsException); - - expect(fixture.mockHub.captureEventCalls.length, 0); - }); - - test('close does get called for user defined client', () async { - final mockHub = MockHub(); - - final mockClient = CloseableMockClient(); - - final client = SentryHttpClient(client: mockClient, hub: mockHub); - client.close(); - - expect(mockHub.addBreadcrumbCalls.length, 0); - expect(mockHub.captureExceptionCalls.length, 0); - verify(mockClient.close()); - }); - - test('no captured span if tracing disabled', () async { - fixture.realHub.options.recordHttpBreadcrumbs = false; - final tr = fixture.realHub.startTransaction( - 'name', - 'op', - bindToScope: true, - ); - - final sut = fixture.getSut( - hub: fixture.realHub, - client: fixture.getClient(statusCode: 200, reason: 'OK'), - ); - final response = await sut.get(requestUri); - - await tr.finish(); - - expect(response.statusCode, 200); - expect(tr, isA()); - }); - - test('captured span if tracing enabled', () async { - fixture.realHub.options.tracesSampleRate = 1.0; - fixture.realHub.options.recordHttpBreadcrumbs = false; - final tr = fixture.realHub.startTransaction( - 'name', - 'op', - bindToScope: true, - ) as SentryTracer; - - final sut = fixture.getSut( - client: fixture.getClient(statusCode: 200, reason: 'OK'), - hub: fixture.realHub, - ); - final response = await sut.get(requestUri); - - await tr.finish(); - - expect(response.statusCode, 200); - expect(tr.children.length, 1); - expect(tr.children.first.context.operation, 'http.client'); - }); - }); -} - -MockClient createThrowingClient() { - return MockClient( - (request) async { - expect(request.url, requestUri); - throw TestException(); - }, - ); -} - -class CloseableMockClient extends Mock implements BaseClient {} - -class Fixture { - late MockHub mockHub; - late Hub realHub; - late MockTransport transport; - final options = defaultTestOptions(); - - Fixture() { - // For some tests the real hub is needed, for other the mock is enough - transport = MockTransport(); - options.transport = transport; - realHub = Hub(options); - mockHub = MockHub(); - } - - SentryHttpClient getSut({ - MockClient? client, - List badStatusCodes = const [], - bool? captureFailedRequests, - Hub? hub, - }) { - final mc = client ?? getClient(); - hub ??= mockHub; - return SentryHttpClient( - client: mc, - hub: hub, - failedRequestStatusCodes: badStatusCodes, - captureFailedRequests: captureFailedRequests, - ); - } - - MockClient getClient({int statusCode = 200, String? reason}) { - return MockClient((request) async { - expect(request.url, requestUri); - return Response('', statusCode, reasonPhrase: reason); - }); - } -} - -class TestException implements Exception {} diff --git a/dart/test/http_client/tracing_client_test.dart b/dart/test/http_client/tracing_client_test.dart deleted file mode 100644 index 2e31e076d7..0000000000 --- a/dart/test/http_client/tracing_client_test.dart +++ /dev/null @@ -1,299 +0,0 @@ -import 'package:http/http.dart'; -import 'package:http/testing.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/http_client/tracing_client.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:test/test.dart'; - -import '../mocks/mock_transport.dart'; -import '../test_utils.dart'; - -final requestUri = Uri.parse('https://example.com?foo=bar#baz'); - -void main() { - group(TracingClient, () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('should add sdk integration on init when tracing is enabled', - () async { - fixture.getSut( - client: fixture.getClient(statusCode: 200, reason: 'OK'), - ); - - expect(fixture._hub.options.isTracingEnabled(), isTrue); - expect(fixture._hub.options.sdk.integrations, - contains(TracingClient.integrationName)); - }); - - test('should not add sdk integration on init when tracing is disabled', - () async { - fixture._hub.options.tracesSampleRate = null; - fixture.getSut( - client: fixture.getClient(statusCode: 200, reason: 'OK'), - ); - - expect(fixture._hub.options.isTracingEnabled(), isFalse); - expect(fixture._hub.options.sdk.integrations, - isNot(contains(TracingClient.integrationName))); - }); - - test('captured span if successful request', () async { - final sut = fixture.getSut( - client: fixture.getClient(statusCode: 200, reason: 'OK'), - ); - final tr = fixture._hub.startTransaction( - 'name', - 'op', - bindToScope: true, - ); - - await sut.get(requestUri); - - await tr.finish(); - - final tracer = (tr as SentryTracer); - final span = tracer.children.first; - - expect(span.status, SpanStatus.ok()); - expect(span.context.operation, 'http.client'); - expect(span.context.description, 'GET https://example.com'); - expect(span.data['http.request.method'], 'GET'); - expect(span.data['url'], 'https://example.com'); - expect(span.data['http.query'], 'foo=bar'); - expect(span.data['http.fragment'], 'baz'); - expect(span.data['http.response.status_code'], 200); - expect(span.data['http.response_content_length'], 2); - expect(span.origin, SentryTraceOrigins.autoHttpHttp); - }); - - test('finish span if errored request', () async { - final sut = fixture.getSut( - client: createThrowingClient(), - ); - final tr = fixture._hub.startTransaction( - 'name', - 'op', - bindToScope: true, - ); - - try { - await sut.get(requestUri); - } catch (_) { - // ignore - } - - await tr.finish(); - - final tracer = (tr as SentryTracer); - final span = tracer.children.first; - - expect(span.finished, isTrue); - }); - - test('associate exception to span if errored request', () async { - final sut = fixture.getSut( - client: createThrowingClient(), - ); - final tr = fixture._hub.startTransaction( - 'name', - 'op', - bindToScope: true, - ); - - dynamic exception; - try { - await sut.get(requestUri); - } catch (error) { - exception = error; - } - - await tr.finish(); - - final tracer = (tr as SentryTracer); - final span = tracer.children.first; - - expect(span.status, SpanStatus.internalError()); - expect(span.throwable, exception); - }); - - test('should add tracing headers from span when tracing enabled', () async { - final sut = fixture.getSut( - client: fixture.getClient(statusCode: 200, reason: 'OK'), - ); - final tr = fixture._hub.startTransaction( - 'name', - 'op', - bindToScope: true, - ); - - final response = await sut.get(requestUri); - - await tr.finish(); - - final tracer = (tr as SentryTracer); - expect(tracer.children.length, 1); - final span = tracer.children.first; - final baggageHeader = span.toBaggageHeader(); - final sentryTraceHeader = span.toSentryTrace(); - - expect( - response.request!.headers[baggageHeader!.name], baggageHeader.value); - expect(response.request!.headers[sentryTraceHeader.name], - sentryTraceHeader.value); - }); - - test( - 'should add tracing headers from propagation context when tracing disabled', - () async { - fixture._hub.options.tracesSampleRate = null; - fixture._hub.options.tracesSampler = null; - final sut = fixture.getSut( - client: fixture.getClient(statusCode: 200, reason: 'OK'), - ); - final propagationContext = fixture._hub.scope.propagationContext; - propagationContext.baggage = SentryBaggage({'foo': 'bar'}); - - final response = await sut.get(requestUri); - - final baggageHeader = propagationContext.toBaggageHeader(); - - expect(propagationContext.toBaggageHeader(), isNotNull); - expect( - response.request!.headers[baggageHeader!.name], baggageHeader.value); - - final traceHeader = SentryTraceHeader.fromTraceHeader( - response.request!.headers['sentry-trace'] as String, - ); - expect(traceHeader.traceId, propagationContext.traceId); - // can't check span id as it is always generated new - }); - - test( - 'tracing header from propagation context should generate new span ids for new events', - () async { - fixture._hub.options.tracesSampleRate = null; - fixture._hub.options.tracesSampler = null; - final sut = fixture.getSut( - client: fixture.getClient(statusCode: 200, reason: 'OK'), - ); - final propagationContext = fixture._hub.scope.propagationContext; - propagationContext.baggage = SentryBaggage({'foo': 'bar'}); - - final response1 = await sut.get(requestUri); - final response2 = await sut.get(requestUri); - - final traceHeader1 = SentryTraceHeader.fromTraceHeader( - response1.request!.headers['sentry-trace']!); - final traceHeader2 = SentryTraceHeader.fromTraceHeader( - response2.request!.headers['sentry-trace']!); - - expect(traceHeader1.spanId, isNot(traceHeader2.spanId)); - }); - - test( - 'should not add tracing headers when URL does not match tracePropagationTargets with tracing enabled', - () async { - final sut = fixture.getSut( - client: fixture.getClient( - statusCode: 200, - reason: 'OK', - ), - tracePropagationTargets: ['nope'], - ); - final tr = fixture._hub.startTransaction( - 'name', - 'op', - bindToScope: true, - ); - - final response = await sut.get(requestUri); - - await tr.finish(); - - final tracer = (tr as SentryTracer); - final span = tracer.children.first; - final baggageHeader = span.toBaggageHeader(); - final sentryTraceHeader = span.toSentryTrace(); - - expect(response.request!.headers[baggageHeader!.name], isNull); - expect(response.request!.headers[sentryTraceHeader.name], isNull); - }); - - test( - 'should not add tracing headers when URL does not match tracePropagationTargets with tracing disabled', - () async { - fixture._hub.options.tracesSampleRate = null; - fixture._hub.options.tracesSampler = null; - final sut = fixture.getSut( - client: fixture.getClient(statusCode: 200, reason: 'OK'), - tracePropagationTargets: ['nope'], - ); - final propagationContext = fixture._hub.scope.propagationContext; - propagationContext.baggage = SentryBaggage({'foo': 'bar'}); - - final response = await sut.get(requestUri); - - final baggageHeader = propagationContext.toBaggageHeader(); - final sentryTraceHeader = propagationContext.toSentryTrace(); - - expect(response.request!.headers[baggageHeader!.name], isNull); - expect(response.request!.headers[sentryTraceHeader.name], isNull); - }); - - test('do not throw if no span bound to the scope', () async { - final sut = fixture.getSut( - client: fixture.getClient(statusCode: 200, reason: 'OK'), - ); - - await sut.get(requestUri); - }); - }); -} - -MockClient createThrowingClient() { - return MockClient( - (request) async { - expect(request.url, requestUri); - throw TestException(); - }, - ); -} - -class Fixture { - final _options = defaultTestOptions(); - late Hub _hub; - final transport = MockTransport(); - Fixture() { - _options.transport = transport; - _options.tracesSampleRate = 1.0; - _hub = Hub(_options); - } - - TracingClient getSut({ - MockClient? client, - List? tracePropagationTargets, - }) { - if (tracePropagationTargets != null) { - _hub.options.tracePropagationTargets.clear(); - _hub.options.tracePropagationTargets.addAll(tracePropagationTargets); - } - final mc = client ?? getClient(); - return TracingClient( - client: mc, - hub: _hub, - ); - } - - MockClient getClient({int statusCode = 200, String? reason}) { - return MockClient((request) async { - expect(request.url, requestUri); - return Response('{}', statusCode, reasonPhrase: reason, request: request); - }); - } -} - -class TestException implements Exception {} diff --git a/dart/test/hub_test.dart b/dart/test/hub_test.dart deleted file mode 100644 index eec9bded6d..0000000000 --- a/dart/test/hub_test.dart +++ /dev/null @@ -1,925 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:mockito/mockito.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/client_reports/discard_reason.dart'; -import 'package:sentry/src/propagation_context.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:sentry/src/transport/data_category.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'mocks.mocks.dart'; -import 'mocks/mock_client_report_recorder.dart'; -import 'mocks/mock_sentry_client.dart'; -import 'test_utils.dart'; - -void main() { - bool scopeEquals(Scope? a, Scope b) { - return identical(a, b) || - a!.level == b.level && - a.transaction == b.transaction && - a.user == b.user && - IterableEquality().equals(a.fingerprint, b.fingerprint) && - IterableEquality().equals(a.breadcrumbs, b.breadcrumbs) && - MapEquality().equals(a.tags, b.tags) && - MapEquality().equals(a.extra, b.extra); - } - - group('Hub instantiation', () { - test('should instantiate with a dsn', () { - final hub = Hub(defaultTestOptions()); - expect(hub.isEnabled, true); - }); - }); - - group('Hub captures', () { - late Fixture fixture; - late SentryEvent fakeEvent; - - setUp(() { - fixture = Fixture(); - fakeEvent = getFakeEvent(); - }); - - test( - 'should capture event with the default scope', - () async { - final hub = fixture.getSut(); - await hub.captureEvent(fakeEvent); - - var scope = fixture.client.captureEventCalls.first.scope; - - expect( - fixture.client.captureEventCalls.first.event, - fakeEvent, - ); - - expect(scopeEquals(scope, Scope(fixture.options)), true); - }, - ); - - test( - 'should capture feedback with the default scope', - () async { - final hub = fixture.getSut(); - final feedback = SentryFeedback(message: 'message'); - await hub.captureFeedback(feedback); - - var scope = fixture.client.captureFeedbackCalls.first.scope; - - expect( - fixture.client.captureFeedbackCalls.first.feedback, - feedback, - ); - - expect(scopeEquals(scope, Scope(fixture.options)), true); - }, - ); - - test('should capture exception', () async { - final hub = fixture.getSut(); - await hub.captureException(fakeException); - - expect(fixture.client.captureEventCalls.length, 1); - expect( - fixture.client.captureEventCalls.first.event.throwable, - fakeException, - ); - expect(fixture.client.captureEventCalls.first.scope, isNotNull); - }); - - test('should capture exception with message', () async { - final hub = fixture.getSut(); - await hub.captureException(fakeException, - message: SentryMessage('Sentry rocks')); - - expect(fixture.client.captureEventCalls.first.event.message?.formatted, - 'Sentry rocks'); - }); - - test('should capture message', () async { - final hub = fixture.getSut(); - final fakeMessage = getFakeMessage(); - - await hub.captureMessage( - fakeMessage.formatted, - level: SentryLevel.warning, - ); - - expect(fixture.client.captureMessageCalls.length, 1); - expect(fixture.client.captureMessageCalls.first.formatted, - fakeMessage.formatted); - expect( - fixture.client.captureMessageCalls.first.level, SentryLevel.warning); - expect(fixture.client.captureMessageCalls.first.scope, isNotNull); - }); - - test('should save the lastEventId', () async { - final hub = fixture.getSut(); - final event = SentryEvent(); - final eventId = event.eventId; - final returnedId = await hub.captureEvent(event); - expect(eventId.toString(), returnedId.toString()); - }); - - test('capture event should assign trace context', () async { - final hub = fixture.getSut(); - - final event = SentryEvent(throwable: fakeException); - final span = NoOpSentrySpan(); - hub.setSpanContext(fakeException, span, 'test'); - - await hub.captureEvent(event); - final capturedEvent = fixture.client.captureEventCalls.first; - - expect(capturedEvent.event.transaction, 'test'); - expect(capturedEvent.event.contexts.trace, isNotNull); - }); - - test('capture exception should assign trace context', () async { - final hub = fixture.getSut(); - - final span = NoOpSentrySpan(); - hub.setSpanContext(fakeException, span, 'test'); - - await hub.captureException(fakeException); - final capturedEvent = fixture.client.captureEventCalls.first; - - expect(capturedEvent.event.transaction, 'test'); - expect(capturedEvent.event.contexts.trace, isNotNull); - }); - - test('capture exception should assign sampled trace context', () async { - final hub = fixture.getSut(); - - final span = SentrySpan( - fixture.tracer, - fixture._context, - hub, - samplingDecision: fixture._context.samplingDecision, - ); - hub.setSpanContext(fakeException, span, 'test'); - - await hub.captureException(fakeException); - final capturedEvent = fixture.client.captureEventCalls.first; - - expect(capturedEvent.event.contexts.trace, isNotNull); - expect(capturedEvent.event.contexts.trace!.sampled, isTrue); - }); - - test('Expando does not throw when exception type is not supported', - () async { - final hub = fixture.getSut(); - - try { - throw 'string error'; - } catch (exception, _) { - final event = SentryEvent(throwable: exception); - final span = NoOpSentrySpan(); - hub.setSpanContext(exception, span, 'test'); - - await hub.captureEvent(event); - } - - final capturedEvent = fixture.client.captureEventCalls.first; - - expect(capturedEvent.event.transaction, 'test'); - expect(capturedEvent.event.contexts.trace, isNotNull); - }); - }); - - group('Hub transactions', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('start transaction with given name, op, desc and start time', - () async { - final hub = fixture.getSut(); - final startTime = DateTime.now(); - - final tr = hub.startTransaction( - 'name', - 'op', - startTimestamp: startTime, - description: 'desc', - ); - - expect(tr.context.operation, 'op'); - expect(tr.context.description, 'desc'); - expect(tr.startTimestamp.isAtSameMomentAs(startTime), true); - expect((tr as SentryTracer).name, 'name'); - expect(tr.origin, SentryTraceOrigins.manual); - }); - - test('start transaction binds span to the scope', () async { - final hub = fixture.getSut(); - - final tr = hub.startTransaction( - 'name', - 'op', - description: 'desc', - bindToScope: true, - ); - - await hub.configureScope((Scope scope) { - expect(scope.span, tr); - }); - }); - - test('start transaction does not bind span to the scope', () async { - final hub = fixture.getSut(); - - hub.startTransaction( - 'name', - 'op', - description: 'desc', - ); - - await hub.configureScope((Scope scope) { - expect(scope.span, isNull); - }); - }); - - test('start transaction samples the transaction', () async { - final hub = fixture.getSut(); - - final tr = hub.startTransaction( - 'name', - 'op', - description: 'desc', - ); - - expect(tr.samplingDecision?.sampled, true); - }); - - test('start transaction does not sample the transaction', () async { - final hub = fixture.getSut(tracesSampleRate: 0.0); - - final tr = hub.startTransaction( - 'name', - 'op', - description: 'desc', - ); - - expect(tr.samplingDecision?.sampled, false); - }); - - test('start transaction runs callback with customSamplingContext', - () async { - double? mySampling(SentrySamplingContext samplingContext) { - expect(samplingContext.customSamplingContext['test'], '1'); - return 0.0; - } - - final hub = fixture.getSut( - tracesSampleRate: null, - tracesSampler: mySampling, - ); - final map = {'test': '1'}; - - final tr = hub.startTransaction( - 'name', - 'op', - description: 'desc', - customSamplingContext: map, - ); - - expect(tr.samplingDecision?.sampled, false); - }); - - test('start transaction respects given sampled', () async { - final hub = fixture.getSut(); - - final tr = hub.startTransactionWithContext( - SentryTransactionContext('name', 'op', - samplingDecision: SentryTracesSamplingDecision(false)), - ); - - expect(tr.samplingDecision?.sampled, false); - }); - - test('start transaction with context sets trace origin fallback', () async { - final hub = fixture.getSut(); - final tr = hub.startTransactionWithContext( - SentryTransactionContext('name', 'op'), - ); - expect(tr.origin, SentryTraceOrigins.manual); - }); - - test('start transaction with context keeps origin', () async { - final hub = fixture.getSut(); - final tr = hub.startTransactionWithContext( - SentryTransactionContext('name', 'op', origin: 'auto.navigation.test'), - ); - expect(tr.origin, 'auto.navigation.test'); - }); - - test('start transaction return NoOp if performance is disabled', () async { - final hub = fixture.getSut(tracesSampleRate: null); - - final tr = hub.startTransaction( - 'name', - 'op', - description: 'desc', - ); - - expect(tr, NoOpSentrySpan()); - }); - - test('get span returns span bound to the scope', () async { - final hub = fixture.getSut(); - - final tr = hub.startTransaction( - 'name', - 'op', - description: 'desc', - bindToScope: true, - ); - - expect(hub.getSpan(), tr); - }); - - test('get span does not return span if not bound to the scope', () async { - final hub = fixture.getSut(); - - hub.startTransaction( - 'name', - 'op', - description: 'desc', - ); - - expect(hub.getSpan(), isNull); - }); - - test('get span does not return span if tracing is disabled', () async { - final hub = fixture.getSut(tracesSampleRate: null); - - hub.startTransaction( - 'name', - 'op', - description: 'desc', - ); - - expect(hub.getSpan(), isNull); - }); - - test('transaction isnt captured if not sampled', () async { - final hub = fixture.getSut(sampled: false); - - var tr = SentryTransaction(fixture.tracer); - final id = await hub.captureTransaction(tr); - - expect(id, SentryId.empty()); - }); - - test('transaction isnt captured if tracing is disabled', () async { - final hub = fixture.getSut(tracesSampleRate: null); - - var tr = SentryTransaction(fixture.tracer); - final id = await hub.captureTransaction(tr); - - expect(id, SentryId.empty()); - }); - - test('transaction is captured', () async { - final hub = fixture.getSut(); - - var tr = SentryTransaction(fixture.tracer); - final id = await hub.captureTransaction(tr); - - expect(id, tr.eventId); - expect(fixture.client.captureTransactionCalls.length, 1); - }); - - test('transaction is captured with traceContext', () async { - final hub = fixture.getSut(); - - var tr = SentryTransaction(fixture.tracer); - final context = SentryTraceContextHeader.fromJson({ - 'trace_id': '${tr.eventId}', - 'public_key': '123', - }); - final id = await hub.captureTransaction(tr, traceContext: context); - - expect(id, tr.eventId); - expect(fixture.client.captureTransactionCalls.length, 1); - expect( - fixture.client.captureTransactionCalls.first.traceContext, context); - }); - - test('captureTransaction hint is passed to client', () async { - final hub = fixture.getSut(); - - var hint = Hint(); - var tr = SentryTransaction(fixture.tracer); - await hub.captureTransaction(tr, hint: hint); - - expect(fixture.client.captureTransactionCalls.first.hint, hint); - }); - - test( - 'startTransactionWithContext sets traceId from scope propagationContext', - () async { - final hub = fixture.getSut(); - - hub.scope.propagationContext = PropagationContext(); - final tr1 = hub.startTransactionWithContext(fixture._context); - expect(tr1.traceContext()?.traceId, hub.scope.propagationContext.traceId); - - hub.scope.propagationContext = PropagationContext(); - final tr2 = hub.startTransactionWithContext(fixture._context); - expect(tr2.traceContext()?.traceId, hub.scope.propagationContext.traceId); - - expect(tr1.traceContext()?.traceId, isNot(tr2.traceContext()?.traceId)); - }); - }); - - group('Hub profiles', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('profiler is not started by default', () async { - final hub = fixture.getSut(); - final tr = hub.startTransaction('name', 'op'); - expect(tr, isA()); - expect((tr as SentryTracer).profiler, isNull); - }); - - test('profiler is started according to the sampling rate', () async { - final hub = fixture.getSut(); - final factory = MockSentryProfilerFactory(); - when(factory.startProfiler(fixture._context)) - .thenReturn(MockSentryProfiler()); - hub.profilerFactory = factory; - - var tr = hub.startTransactionWithContext(fixture._context); - expect((tr as SentryTracer).profiler, isNull); - verifyZeroInteractions(factory); - - hub.options.profilesSampleRate = 1.0; - tr = hub.startTransactionWithContext(fixture._context); - expect((tr as SentryTracer).profiler, isNotNull); - verify(factory.startProfiler(fixture._context)).called(1); - }); - - test('profiler.finish() is called', () async { - final hub = fixture.getSut(); - final factory = MockSentryProfilerFactory(); - final profiler = MockSentryProfiler(); - final expected = MockSentryProfileInfo(); - when(factory.startProfiler(fixture._context)).thenReturn(profiler); - when(profiler.finishFor(any)).thenAnswer((_) async => expected); - - hub.profilerFactory = factory; - hub.options.profilesSampleRate = 1.0; - final tr = hub.startTransactionWithContext(fixture._context); - await tr.finish(); - verify(profiler.finishFor(any)).called(1); - verify(profiler.dispose()).called(1); - }); - - test('profiler.dispose() is called even if not captured', () async { - final hub = fixture.getSut(); - final factory = MockSentryProfilerFactory(); - final profiler = MockSentryProfiler(); - final expected = MockSentryProfileInfo(); - when(factory.startProfiler(fixture._context)).thenReturn(profiler); - when(profiler.finishFor(any)).thenAnswer((_) async => expected); - - hub.profilerFactory = factory; - hub.options.profilesSampleRate = 1.0; - final tr = hub.startTransactionWithContext(fixture._context); - await tr.finish(status: SpanStatus.aborted()); - verify(profiler.dispose()).called(1); - verifyNever(profiler.finishFor(any)); - }); - }); - - group('Hub scope', () { - var hub = Hub(defaultTestOptions()); - var client = MockSentryClient(); - late SentryEvent fakeEvent; - late SentryUser fakeUser; - - setUp(() { - hub = Hub(defaultTestOptions()); - client = MockSentryClient(); - hub.bindClient(client); - fakeEvent = getFakeEvent(); - fakeUser = getFakeUser(); - }); - - test('returns scope', () async { - final scope = hub.scope; - expect(scope, isNotNull); - }); - - test('should configure its scope', () async { - await hub.configureScope((Scope scope) { - scope - ..level = SentryLevel.debug - ..fingerprint = ['1', '2']; - - scope.setUser(fakeUser); - }); - await hub.captureEvent(fakeEvent); - - expect(client.captureEventCalls.isNotEmpty, true); - expect(client.captureEventCalls.first.event, fakeEvent); - expect(client.captureEventCalls.first.scope, isNotNull); - final scope = client.captureEventCalls.first.scope; - - final otherScope = Scope(defaultTestOptions()) - ..level = SentryLevel.debug - ..fingerprint = ['1', '2']; - - await otherScope.setUser(fakeUser); - - expect( - scopeEquals( - scope, - otherScope, - ), - true, - ); - }); - - test('should configure scope async', () async { - await hub.configureScope((Scope scope) async { - await Future.delayed(Duration(milliseconds: 10)); - return scope.setUser(fakeUser); - }); - - await hub.captureEvent(fakeEvent); - - final scope = client.captureEventCalls.first.scope; - final otherScope = Scope(defaultTestOptions()); - await otherScope.setUser(fakeUser); - - expect( - scopeEquals( - scope, - otherScope, - ), - true); - }); - - test('should add breadcrumb to current Scope', () async { - await hub.configureScope((Scope scope) { - expect(0, scope.breadcrumbs.length); - }); - await hub.addBreadcrumb(Breadcrumb(message: 'test')); - await hub.configureScope((Scope scope) { - expect(1, scope.breadcrumbs.length); - expect('test', scope.breadcrumbs.first.message); - }); - }); - - test('generateNewTrace creates new trace id in propagation context', () { - final oldTraceId = hub.scope.propagationContext.traceId; - - hub.generateNewTrace(); - - final newTraceId = hub.scope.propagationContext.traceId; - expect(oldTraceId, isNot(newTraceId)); - }); - - test('generateNewTrace resets sampleRand in propagation context', () { - hub.scope.propagationContext.sampleRand = 1.0; - - hub.generateNewTrace(); - - final newSampleRand = hub.scope.propagationContext.sampleRand; - expect(newSampleRand, isNull); - }); - }); - - group('Hub scope callback', () { - late Fixture fixture; - late SentryEvent fakeEvent; - - setUp(() { - fixture = Fixture(); - fakeEvent = getFakeEvent(); - }); - - test('captureEvent should handle thrown error in scope callback', () async { - fixture.options.automatedTestMode = false; - final hub = fixture.getSut(debug: true); - final scopeCallbackException = Exception('error in scope callback'); - - ScopeCallback scopeCallback = (Scope scope) { - throw scopeCallbackException; - }; - - await hub.captureEvent(fakeEvent, withScope: scopeCallback); - - expect(fixture.loggedException, scopeCallbackException); - expect(fixture.loggedLevel, SentryLevel.error); - }); - - test('captureFeedback should handle thrown error in scope callback', - () async { - fixture.options.automatedTestMode = false; - final hub = fixture.getSut(debug: true); - final scopeCallbackException = Exception('error in scope callback'); - - ScopeCallback scopeCallback = (Scope scope) { - throw scopeCallbackException; - }; - - final feedback = SentryFeedback(message: 'message'); - await hub.captureFeedback(feedback, withScope: scopeCallback); - - expect(fixture.loggedException, scopeCallbackException); - expect(fixture.loggedLevel, SentryLevel.error); - }); - - test('captureException should handle thrown error in scope callback', - () async { - fixture.options.automatedTestMode = false; - final hub = fixture.getSut(debug: true); - final scopeCallbackException = Exception('error in scope callback'); - - ScopeCallback scopeCallback = (Scope scope) { - throw scopeCallbackException; - }; - - final exception = Exception("captured exception"); - await hub.captureException(exception, withScope: scopeCallback); - - expect(fixture.loggedException, scopeCallbackException); - expect(fixture.loggedLevel, SentryLevel.error); - }); - - test('captureMessage should handle thrown error in scope callback', - () async { - fixture.options.automatedTestMode = false; - final hub = fixture.getSut(debug: true); - final scopeCallbackException = Exception('error in scope callback'); - - ScopeCallback scopeCallback = (Scope scope) { - throw scopeCallbackException; - }; - - await hub.captureMessage("captured message", withScope: scopeCallback); - - expect(fixture.loggedException, scopeCallbackException); - expect(fixture.loggedLevel, SentryLevel.error); - }); - }); - - group('Hub Client', () { - late Hub hub; - late SentryClient client; - SentryOptions options; - late SentryEvent fakeEvent; - - setUp(() { - options = defaultTestOptions(); - fakeEvent = getFakeEvent(); - hub = Hub(options); - client = MockSentryClient(); - hub.bindClient(client); - }); - - test('should bind a new client', () async { - final client2 = MockSentryClient(); - hub.bindClient(client2); - await hub.captureEvent(fakeEvent); - expect(client2.captureEventCalls.length, 1); - expect(client2.captureEventCalls.first.event, fakeEvent); - expect(client2.captureEventCalls.first.scope, isNotNull); - }); - - test('should close its client', () async { - await hub.close(); - - expect(hub.isEnabled, false); - expect((client as MockSentryClient).closeCalls, 1); - }); - }); - - test('clones', () { - // TODO I'm not sure how to test it - // could we set [hub.stack] as @visibleForTesting ? - }); - - group('Hub withScope', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('captureEvent should create a new scope', () async { - final hub = fixture.getSut(); - await hub.captureEvent(SentryEvent()); - await hub.captureEvent(SentryEvent(), withScope: (scope) async { - await scope.setUser(SentryUser(id: 'foo bar')); - }); - await hub.captureEvent(SentryEvent()); - - var calls = fixture.client.captureEventCalls; - expect(calls.length, 3); - expect(calls[0].scope?.user, isNull); - expect(calls[1].scope?.user?.id, 'foo bar'); - expect(calls[2].scope?.user, isNull); - }); - - test('captureFeedback should create a new scope', () async { - final hub = fixture.getSut(); - await hub.captureFeedback(SentryFeedback(message: 'message')); - await hub.captureFeedback(SentryFeedback(message: 'message'), - withScope: (scope) async { - await scope.setUser(SentryUser(id: 'foo bar')); - }); - await hub.captureFeedback(SentryFeedback(message: 'message')); - - var calls = fixture.client.captureFeedbackCalls; - expect(calls.length, 3); - expect(calls[0].scope?.user, isNull); - expect(calls[1].scope?.user?.id, 'foo bar'); - expect(calls[2].scope?.user, isNull); - }); - - test('captureException should create a new scope', () async { - final hub = fixture.getSut(); - await hub.captureException(Exception('0')); - await hub.captureException(Exception('1'), withScope: (scope) async { - await scope.setUser(SentryUser(id: 'foo bar')); - }); - await hub.captureException(Exception('2')); - - var calls = fixture.client.captureEventCalls; - expect(calls.length, 3); - expect(calls[0].scope?.user, isNull); - expect(calls[0].event.throwable?.toString(), 'Exception: 0'); - - expect(calls[1].scope?.user?.id, 'foo bar'); - expect(calls[1].event.throwable?.toString(), 'Exception: 1'); - - expect(calls[2].scope?.user, isNull); - expect(calls[2].event.throwable?.toString(), 'Exception: 2'); - }); - - test('captureMessage should create a new scope', () async { - final hub = fixture.getSut(); - await hub.captureMessage('foo bar 0'); - await hub.captureMessage('foo bar 1', withScope: (scope) async { - await scope.setUser(SentryUser(id: 'foo bar')); - }); - await hub.captureMessage('foo bar 2'); - - var calls = fixture.client.captureMessageCalls; - expect(calls.length, 3); - expect(calls[0].scope?.user, isNull); - expect(calls[0].formatted, 'foo bar 0'); - - expect(calls[1].scope?.user?.id, 'foo bar'); - expect(calls[1].formatted, 'foo bar 1'); - - expect(calls[2].scope?.user, isNull); - expect(calls[2].formatted, 'foo bar 2'); - }); - - test( - 'withScope should use the same propagation context as the current scope', - () async { - final hub = fixture.getSut(); - late Scope clonedScope; - final currentScope = hub.scope; - await hub.captureEvent(SentryEvent(), withScope: (scope) async { - clonedScope = scope; - }); - - // Verify the propagation context is shared (same instance) - expect( - identical( - clonedScope.propagationContext, currentScope.propagationContext), - true, - reason: 'Propagation context should be the same instance'); - expect(clonedScope.propagationContext.traceId, - currentScope.propagationContext.traceId); - }); - }); - - group('ClientReportRecorder', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('record sample rate dropping transaction', () async { - final hub = fixture.getSut(sampled: false); - var transaction = SentryTransaction(fixture.tracer); - fixture.tracer.startChild('child1'); - fixture.tracer.startChild('child2'); - fixture.tracer.startChild('child3'); - - await hub.captureTransaction(transaction); - - expect(fixture.recorder.discardedEvents.length, 2); - - // we dropped the whole tracer and it has 3 span children so the span count should be 4 - // 3 children + 1 root span - final spanCount = fixture.recorder.discardedEvents - .firstWhere((element) => - element.category == DataCategory.span && - element.reason == DiscardReason.sampleRate) - .quantity; - expect(spanCount, 4); - }); - }); - - group('Hub Logs', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - SentryLog givenLog() { - return SentryLog( - timestamp: DateTime.now(), - traceId: SentryId.newId(), - level: SentryLogLevel.info, - body: 'test', - attributes: { - 'attribute': SentryLogAttribute.string('value'), - }, - ); - } - - test('captures logs', () async { - final hub = fixture.getSut(); - - final log = givenLog(); - await hub.captureLog(log); - - expect(fixture.client.captureLogCalls.length, 1); - expect(fixture.client.captureLogCalls.first.log, log); - }); - }); -} - -class Fixture { - final client = MockSentryClient(); - final recorder = MockClientReportRecorder(); - - final options = defaultTestOptions(); - late SentryTransactionContext _context; - late SentryTracer tracer; - - SentryLevel? loggedLevel; - String? loggedMessage; - Object? loggedException; - - Hub getSut({ - double? tracesSampleRate = 1.0, - TracesSamplerCallback? tracesSampler, - bool? sampled = true, - bool debug = false, - }) { - options.tracesSampleRate = tracesSampleRate; - options.tracesSampler = tracesSampler; - options.debug = debug; - options.log = mockLogger; // Enable logging in DiagnosticsLogger - - final hub = Hub(options); - - // A fully configured context - won't trigger a copy in startTransaction(). - _context = SentryTransactionContext( - 'name', - 'op', - samplingDecision: SentryTracesSamplingDecision(sampled!), - origin: SentryTraceOrigins.manual, - ); - - tracer = SentryTracer(_context, hub); - - hub.bindClient(client); - options.recorder = recorder; - - return hub; - } - - void mockLogger( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - loggedLevel = level; - loggedMessage = message; - loggedException = exception; - } -} diff --git a/dart/test/initialization_test.dart b/dart/test/initialization_test.dart deleted file mode 100644 index 46c0cd35c6..0000000000 --- a/dart/test/initialization_test.dart +++ /dev/null @@ -1,57 +0,0 @@ -@TestOn('vm') -library; - -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'test_utils.dart'; - -// Tests for the following issue -// https://github.com/getsentry/sentry-dart/issues/508 -// There are no asserts, test are succesfull if no exceptions are thrown. -void main() { - tearDown(() async { - await Sentry.close(); - }); - - test('async re-initilization', () async { - final options = defaultTestOptions(); - await Sentry.init( - (options) { - options.dsn = fakeDsn; - }, - options: options, - ); - - await Sentry.close(); - - await Sentry.init( - (options) { - options.dsn = fakeDsn; - }, - options: options, - ); - }); - - // This is the failure from - // https://github.com/getsentry/sentry-dart/issues/508 - test('re-initilization', () async { - final options = defaultTestOptions(); - await Sentry.init( - (options) { - options.dsn = fakeDsn; - }, - options: options, - ); - - await Sentry.close(); - - await Sentry.init( - (options) { - options.dsn = fakeDsn; - }, - options: options, - ); - }); -} diff --git a/dart/test/load_dart_debug_images_integration_test.dart b/dart/test/load_dart_debug_images_integration_test.dart deleted file mode 100644 index 87bc836cf7..0000000000 --- a/dart/test/load_dart_debug_images_integration_test.dart +++ /dev/null @@ -1,310 +0,0 @@ -@TestOn('vm') -library; - -import 'dart:async'; - -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/load_dart_debug_images_integration.dart'; -import 'package:sentry/src/platform/mock_platform.dart'; -import 'package:sentry/src/sentry_stack_trace_factory.dart'; -import 'package:test/test.dart'; - -import 'mocks/mock_runtime_checker.dart'; -import 'test_utils.dart'; - -void main() { - final platforms = [ - MockPlatform.iOS(), - MockPlatform.macOS(), - MockPlatform.android(), - MockPlatform.windows(), - ]; - - for (final platform in platforms) { - group('$LoadDartDebugImagesIntegration $platform', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - fixture.options.platform = platform; - fixture.callIntegration(); - }); - - test('adds itself to sdk.integrations', () { - expect( - fixture.options.sdk.integrations - .contains(LoadDartDebugImagesIntegration.integrationName), - true, - ); - }); - - test('Event processor is added to options', () { - expect(fixture.options.eventProcessors.length, 1); - expect( - fixture.options.eventProcessors.first.runtimeType.toString(), - 'LoadDartDebugImagesIntegrationEventProcessor', - ); - }); - - test( - 'Event processor does not add debug image if symbolication is not needed', - () async { - final event = fixture.newEvent(needsSymbolication: false); - final resultEvent = await fixture.process(event); - - expect(resultEvent, equals(event)); - }); - - test('Event processor does not add debug image if stackTrace is null', - () async { - final event = fixture.newEvent(); - final resultEvent = await fixture.process(event); - - expect(resultEvent, equals(event)); - }); - - test( - 'Event processor does not add debug image if enableDartSymbolication is false', - () async { - fixture.options.enableDartSymbolication = false; - final event = fixture.newEvent(); - final resultEvent = await fixture.process(event); - - expect(resultEvent, equals(event)); - }); - - test('Event processor adds debug image when symbolication is needed', - () async { - final debugImage = await fixture.parseAndProcess(''' -*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** -build_id: 'b680cb890f9e3c12a24b172d050dec73' -isolate_dso_base: 10000000 - #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 -'''); - expect(debugImage?.debugId, isNotEmpty); - expect(debugImage?.imageAddr, equals('0x10000000')); - }); - - test( - 'Event processor does not add debug image on second stack trace without image address', - () async { - final debugImage = await fixture.parseAndProcess(''' -*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** -build_id: 'b680cb890f9e3c12a24b172d050dec73' -isolate_dso_base: 10000000 - #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 -'''); - expect(debugImage?.debugId, isNotEmpty); - expect(debugImage?.imageAddr, equals('0x10000000')); - - final event = fixture.newEvent(stackTrace: fixture.parse(''' -*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** - #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 -''')); - final resultEvent = await fixture.process(event); - expect(resultEvent?.debugMeta?.images, isEmpty); - }); - - test('returns null for invalid stack trace', () async { - final event = - fixture.newEvent(stackTrace: fixture.parse('Invalid stack trace')); - final resultEvent = await fixture.process(event); - expect(resultEvent?.debugMeta?.images, isEmpty); - }); - - test('extracts correct debug ID with short debugId', () async { - final debugImage = await fixture.parseAndProcess(''' -*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** -build_id: 'b680cb890f9e3c12a24b172d050dec73' -isolate_dso_base: 20000000 - #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 -'''); - - if (platform.isIOS || platform.isMacOS) { - expect(debugImage?.debugId, 'b680cb89-0f9e-3c12-a24b-172d050dec73'); - } else { - expect(debugImage?.debugId, '89cb80b6-9e0f-123c-a24b-172d050dec73'); - } - }); - - test('extracts correct debug ID for Android with long debugId', () async { - final debugImage = await fixture.parseAndProcess(''' -*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** -build_id: 'f1c3bcc0279865fe3058404b2831d9e64135386c' -isolate_dso_base: 30000000 - #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 -'''); - - expect(debugImage?.debugId, - equals('c0bcc3f1-9827-fe65-3058-404b2831d9e6')); - }, skip: !platform.isAndroid); - - test('sets correct type based on platform', () async { - final debugImage = await fixture.parseAndProcess(''' -*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** -build_id: 'b680cb890f9e3c12a24b172d050dec73' -isolate_dso_base: 40000000 - #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 -'''); - - if (platform.isAndroid || platform.isWindows) { - expect(debugImage?.type, 'elf'); - } else if (platform.isIOS || platform.isMacOS) { - expect(debugImage?.type, 'macho'); - } else { - fail('missing case for platform $platform'); - } - }); - - test('sets codeFile based on platform', () async { - final debugImage = await fixture.parseAndProcess(''' -*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** -build_id: 'b680cb890f9e3c12a24b172d050dec73' -isolate_dso_base: 40000000 - #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 -'''); - - if (platform.isAndroid) { - expect(debugImage?.codeFile, 'libapp.so'); - } else if (platform.isWindows) { - expect(debugImage?.codeFile, 'data/app.so'); - } else if (platform.isIOS || platform.isMacOS) { - expect(debugImage?.codeFile, 'App.Framework/App'); - } else { - fail('missing case for platform $platform'); - } - }); - - test('debugImage is cached after first extraction', () async { - final stackTrace = ''' -*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** -build_id: 'b680cb890f9e3c12a24b172d050dec73' -isolate_dso_base: 10000000 - #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 -'''; - // First extraction - final debugImage1 = await fixture.parseAndProcess(stackTrace); - expect(debugImage1, isNotNull); - - // Second extraction - final debugImage2 = await fixture.parseAndProcess(stackTrace); - expect(debugImage2, equals(debugImage1)); - }); - }); - } - - test('does add itself to sdk.integrations if split debug info is true', () { - final fixture = Fixture() - ..options.runtimeChecker = MockRuntimeChecker(isSplitDebugInfo: true); - fixture.callIntegration(); - expect( - fixture.options.sdk.integrations - .contains(LoadDartDebugImagesIntegration.integrationName), - isTrue, - ); - }); - - test('does add itself to sdk.integrations if obfuscation is true', () { - final fixture = Fixture() - ..options.runtimeChecker = MockRuntimeChecker(isObfuscated: true); - fixture.callIntegration(); - expect( - fixture.options.sdk.integrations - .contains(LoadDartDebugImagesIntegration.integrationName), - isTrue, - ); - }); - - test( - 'does not add itself to sdk.integrations if app obfuscation and split debug info is false', - () { - final fixture = Fixture()..options.runtimeChecker = MockRuntimeChecker(); - fixture.callIntegration(); - expect( - fixture.options.sdk.integrations - .contains(LoadDartDebugImagesIntegration.integrationName), - false, - ); - }); - - test('does add event processor to options if split debug info is true', () { - final fixture = Fixture() - ..options.runtimeChecker = MockRuntimeChecker(isSplitDebugInfo: true); - fixture.callIntegration(); - expect(fixture.options.eventProcessors.length, 1); - }); - - test('does add event processor to options if obfuscation is true', () { - final fixture = Fixture() - ..options.runtimeChecker = MockRuntimeChecker(isObfuscated: true); - fixture.callIntegration(); - expect(fixture.options.eventProcessors.length, 1); - }); - - test( - 'does not add event processor to options if app obfuscation and split debug info is false', - () { - final fixture = Fixture()..options.runtimeChecker = MockRuntimeChecker(); - fixture.callIntegration(); - expect(fixture.options.eventProcessors.length, 0); - }); - - test('does not add itself to sdk.integrations if platform is web', () { - final fixture = Fixture() - ..options.runtimeChecker = MockRuntimeChecker(isObfuscated: true) - ..options.platform = MockPlatform(isWeb: true); - fixture.callIntegration(); - expect( - fixture.options.sdk.integrations - .contains(LoadDartDebugImagesIntegration.integrationName), - false, - ); - }); - - test('debug image is null on unsupported platforms', () async { - final fixture = Fixture()..options.platform = MockPlatform.linux(); - fixture.callIntegration(); - final event = fixture.newEvent(stackTrace: fixture.parse(''' -*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** -build_id: 'b680cb890f9e3c12a24b172d050dec73' -isolate_dso_base: 40000000 - #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 -''')); - final resultEvent = await fixture.process(event); - expect(resultEvent?.debugMeta?.images.length, 0); - }); -} - -class Fixture { - final options = defaultTestOptions() - ..runtimeChecker = - MockRuntimeChecker(isObfuscated: true, isSplitDebugInfo: true); - late final factory = SentryStackTraceFactory(options); - - void callIntegration() { - final integration = LoadDartDebugImagesIntegration(); - integration.call(Hub(options), options); - } - - SentryStackTrace parse(String stacktrace) => factory.parse(stacktrace); - - SentryEvent newEvent( - {bool needsSymbolication = true, SentryStackTrace? stackTrace}) { - stackTrace ??= SentryStackTrace(frames: [ - SentryStackFrame(platform: needsSymbolication ? null : 'dart') - ]); - return SentryEvent( - threads: [SentryThread(stacktrace: stackTrace)], - debugMeta: DebugMeta()); - } - - FutureOr process(SentryEvent event) => - options.eventProcessors.first.apply(event, Hint()); - - Future parseAndProcess(String stacktrace) async { - final event = newEvent(stackTrace: parse(stacktrace)); - final resultEvent = await process(event); - expect(resultEvent?.debugMeta?.images.length, 1); - return resultEvent?.debugMeta?.images.first; - } -} diff --git a/dart/test/logs_enricher_interation_test.dart b/dart/test/logs_enricher_interation_test.dart deleted file mode 100644 index 78eda1b6a4..0000000000 --- a/dart/test/logs_enricher_interation_test.dart +++ /dev/null @@ -1,99 +0,0 @@ -@TestOn('vm') -library; - -import 'package:sentry/src/logs_enricher_integration.dart'; -import 'package:test/test.dart'; -import 'package:sentry/src/hub.dart'; -import 'package:sentry/src/protocol/sentry_log.dart'; -import 'package:sentry/src/protocol/sentry_log_attribute.dart'; -import 'package:sentry/src/protocol/sentry_id.dart'; -import 'package:sentry/src/protocol/sentry_log_level.dart'; -import 'test_utils.dart'; -import 'package:sentry/src/utils/os_utils.dart'; - -void main() { - SentryLog givenLog() { - return SentryLog( - timestamp: DateTime.now(), - traceId: SentryId.newId(), - level: SentryLogLevel.info, - body: 'test', - attributes: { - 'attribute': SentryLogAttribute.string('value'), - }, - ); - } - - group('LogsEnricherIntegration', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('adds itself to sdk.integrations if enableLogs is true', () { - fixture.options.enableLogs = true; - fixture.addIntegration(); - - expect( - fixture.options.sdk.integrations - .contains(LogsEnricherIntegration.integrationName), - true, - ); - }); - - test('does not add itself to sdk.integrations if enableLogs is false', () { - fixture.options.enableLogs = false; - fixture.addIntegration(); - - expect( - fixture.options.sdk.integrations - .contains(LogsEnricherIntegration.integrationName), - false, - ); - }); - - test( - 'adds os.name and os.version to log attributes on OnBeforeCaptureLog lifecycle event', - () async { - fixture.options.enableLogs = true; - fixture.addIntegration(); - - final log = givenLog(); - await fixture.hub.captureLog(log); - - final os = getSentryOperatingSystem(); - - expect(log.attributes['os.name']?.value, os.name); - expect(log.attributes['os.version']?.value, os.version); - }); - - test( - 'does not add os.name and os.version to log attributes if enableLogs is false', - () async { - fixture.options.enableLogs = false; - fixture.addIntegration(); - - final log = givenLog(); - await fixture.hub.captureLog(log); - - expect(log.attributes['os.name'], isNull); - expect(log.attributes['os.version'], isNull); - }); - }); -} - -class Fixture { - final options = defaultTestOptions(); - late final hub = Hub(options); - late final LogsEnricherIntegration integration; - - Fixture() { - options.enableLogs = true; - integration = LogsEnricherIntegration(); - } - - void addIntegration() { - integration.call(hub, options); - } -} diff --git a/dart/test/mocks.dart b/dart/test/mocks.dart deleted file mode 100644 index 22900c657a..0000000000 --- a/dart/test/mocks.dart +++ /dev/null @@ -1,194 +0,0 @@ -import 'package:mockito/annotations.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/profiling.dart'; -import 'package:sentry/src/transport/rate_limiter.dart'; - -final fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; - -final fakeException = Exception('Error'); - -SentryMessage getFakeMessage() { - return SentryMessage( - 'message 1', - template: 'message %d', - params: ['1'], - ); -} - -SentryUser getFakeUser() { - return SentryUser(id: '1', email: 'test@test'); -} - -SentryEvent getFakeEvent() { - return SentryEvent( - logger: 'main', - serverName: 'server.dart', - release: '1.4.0-preview.1', - environment: 'Test', - message: SentryMessage('This is an example Dart event.'), - transaction: '/example/app', - level: SentryLevel.warning, - tags: const {'project-id': '7371'}, - // ignore: deprecated_member_use_from_same_package - extra: const {'company-name': 'Dart Inc'}, - fingerprint: const ['example-dart'], - modules: const {'module1': 'factory'}, - sdk: SdkVersion(name: 'sdk1', version: '1.0.0'), - user: SentryUser( - id: '800', - username: 'first-user', - email: 'first@user.lan', - ipAddress: '127.0.0.1', - data: {'first-sign-in': '2020-01-01'}, - ), - breadcrumbs: [ - Breadcrumb( - message: 'UI Lifecycle', - timestamp: DateTime.now().toUtc(), - category: 'ui.lifecycle', - type: 'navigation', - data: {'screen': 'MainActivity', 'state': 'created'}, - level: SentryLevel.info, - ) - ], - contexts: Contexts( - operatingSystem: SentryOperatingSystem( - name: 'Android', - version: '5.0.2', - build: 'LRX22G.P900XXS0BPL2', - kernelVersion: - 'Linux version 3.4.39-5726670 (dpi@SWHC3807) (gcc version 4.8 (GCC) ) #1 SMP PREEMPT Thu Dec 1 19:42:39 KST 2016', - rooted: false, - ), - runtimes: [SentryRuntime(name: 'ART', version: '5')], - app: SentryApp( - name: 'Example Dart App', - version: '1.42.0', - identifier: 'HGT-App-13', - build: '93785', - buildType: 'release', - deviceAppHash: '5afd3a6', - startTime: DateTime.now().toUtc(), - ), - browser: SentryBrowser( - name: 'Firefox', - version: '42.0.1', - ), - device: SentryDevice( - name: 'SM-P900', - family: 'SM-P900', - model: 'SM-P900 (LRX22G)', - modelId: 'LRX22G', - arch: 'armeabi-v7a', - batteryLevel: 99, - orientation: SentryOrientation.landscape, - manufacturer: 'samsung', - brand: 'samsung', - screenDensity: 2.1, - screenDpi: 320, - online: true, - charging: true, - lowMemory: true, - simulator: false, - memorySize: 1500, - freeMemory: 200, - usableMemory: 4294967296, - storageSize: 4294967296, - freeStorage: 2147483648, - externalStorageSize: 8589934592, - externalFreeStorage: 2863311530, - bootTime: DateTime.now().toUtc(), - ), - ), - ); -} - -/// Always returns null and thus drops all events -class DropAllEventProcessor implements EventProcessor { - @override - SentryEvent? apply(SentryEvent event, Hint hint) { - return null; - } -} - -class DropSpansEventProcessor implements EventProcessor { - DropSpansEventProcessor(this.numberOfSpansToDrop); - - final int numberOfSpansToDrop; - - @override - SentryEvent? apply(SentryEvent event, Hint hint) { - if (event is SentryTransaction) { - if (numberOfSpansToDrop > event.spans.length) { - throw ArgumentError( - 'numberOfSpansToDrop must be less than the number of spans in the transaction'); - } - final droppedSpans = event.spans.take(numberOfSpansToDrop).toList(); - event.spans.removeWhere((element) => droppedSpans.contains(element)); - } - return event; - } -} - -class FunctionEventProcessor implements EventProcessor { - FunctionEventProcessor(this.applyFunction); - - final EventProcessorFunction applyFunction; - - @override - SentryEvent? apply(SentryEvent event, Hint hint) { - return applyFunction(event, hint); - } -} - -typedef EventProcessorFunction = SentryEvent? Function( - SentryEvent event, Hint hint); - -SentryEnvelope getFakeEnvelope() { - return SentryEnvelope.fromEvent( - getFakeEvent(), - SdkVersion(name: 'sdk1', version: '1.0.0'), - dsn: fakeDsn, - ); -} - -class MockRateLimiter implements RateLimiter { - bool filterReturnsNull = false; - SentryEnvelope? filteredEnvelope; - SentryEnvelope? envelopeToFilter; - - String? sentryRateLimitHeader; - String? retryAfterHeader; - int? errorCode; - - @override - SentryEnvelope? filter(SentryEnvelope envelope) { - if (filterReturnsNull) { - return null; - } - envelopeToFilter = envelope; - return filteredEnvelope ?? envelope; - } - - @override - void updateRetryAfterLimits( - String? sentryRateLimitHeader, String? retryAfterHeader, int errorCode) { - this.sentryRateLimitHeader = sentryRateLimitHeader; - this.retryAfterHeader = retryAfterHeader; - this.errorCode = errorCode; - } -} - -final Map testUnknown = { - 'unknown-string': 'foo', - 'unknown-bool': true, - 'unknown-num': 9001, -}; - -@GenerateMocks([ - SentryProfilerFactory, - SentryProfiler, - SentryProfileInfo, - ExceptionTypeIdentifier, -]) -void main() {} diff --git a/dart/test/mocks.mocks.dart b/dart/test/mocks.mocks.dart deleted file mode 100644 index efc78832af..0000000000 --- a/dart/test/mocks.mocks.dart +++ /dev/null @@ -1,115 +0,0 @@ -// Mocks generated by Mockito 5.4.6 from annotations -// in sentry/test/mocks.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i4; - -import 'package:mockito/mockito.dart' as _i1; -import 'package:sentry/sentry.dart' as _i2; -import 'package:sentry/src/profiling.dart' as _i3; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: deprecated_member_use -// ignore_for_file: deprecated_member_use_from_same_package -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: must_be_immutable -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -class _FakeSentryEnvelopeItem_0 extends _i1.SmartFake - implements _i2.SentryEnvelopeItem { - _FakeSentryEnvelopeItem_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -/// A class which mocks [SentryProfilerFactory]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockSentryProfilerFactory extends _i1.Mock - implements _i3.SentryProfilerFactory { - MockSentryProfilerFactory() { - _i1.throwOnMissingStub(this); - } - - @override - _i3.SentryProfiler? startProfiler(_i2.SentryTransactionContext? context) => - (super.noSuchMethod(Invocation.method( - #startProfiler, - [context], - )) as _i3.SentryProfiler?); -} - -/// A class which mocks [SentryProfiler]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockSentryProfiler extends _i1.Mock implements _i3.SentryProfiler { - MockSentryProfiler() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Future<_i3.SentryProfileInfo?> finishFor( - _i2.SentryTransaction? transaction) => - (super.noSuchMethod( - Invocation.method( - #finishFor, - [transaction], - ), - returnValue: _i4.Future<_i3.SentryProfileInfo?>.value(), - ) as _i4.Future<_i3.SentryProfileInfo?>); - - @override - void dispose() => super.noSuchMethod( - Invocation.method( - #dispose, - [], - ), - returnValueForMissingStub: null, - ); -} - -/// A class which mocks [SentryProfileInfo]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockSentryProfileInfo extends _i1.Mock implements _i3.SentryProfileInfo { - MockSentryProfileInfo() { - _i1.throwOnMissingStub(this); - } - - @override - _i2.SentryEnvelopeItem asEnvelopeItem() => (super.noSuchMethod( - Invocation.method( - #asEnvelopeItem, - [], - ), - returnValue: _FakeSentryEnvelopeItem_0( - this, - Invocation.method( - #asEnvelopeItem, - [], - ), - ), - ) as _i2.SentryEnvelopeItem); -} - -/// A class which mocks [ExceptionTypeIdentifier]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockExceptionTypeIdentifier extends _i1.Mock - implements _i2.ExceptionTypeIdentifier { - MockExceptionTypeIdentifier() { - _i1.throwOnMissingStub(this); - } -} diff --git a/dart/test/mocks/mock_client_report_recorder.dart b/dart/test/mocks/mock_client_report_recorder.dart deleted file mode 100644 index 4d8eaa5b1d..0000000000 --- a/dart/test/mocks/mock_client_report_recorder.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:sentry/src/client_reports/client_report_recorder.dart'; -import 'package:sentry/src/client_reports/discard_reason.dart'; -import 'package:sentry/src/client_reports/client_report.dart'; -import 'package:sentry/src/client_reports/discarded_event.dart'; -import 'package:sentry/src/transport/data_category.dart'; - -class MockClientReportRecorder implements ClientReportRecorder { - List discardedEvents = []; - - ClientReport? clientReport; - - bool flushCalled = false; - - @override - ClientReport? flush() { - flushCalled = true; - return clientReport; - } - - @override - void recordLostEvent(DiscardReason reason, DataCategory category, - {int count = 1}) { - discardedEvents.add(DiscardedEvent(reason, category, count)); - } -} diff --git a/dart/test/mocks/mock_envelope.dart b/dart/test/mocks/mock_envelope.dart deleted file mode 100644 index 1009f2e396..0000000000 --- a/dart/test/mocks/mock_envelope.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/sentry_envelope_header.dart'; -import 'package:sentry/src/client_reports/client_report.dart'; - -class MockEnvelope implements SentryEnvelope { - ClientReport? clientReport; - - @override - void addClientReport(ClientReport? clientReport) { - this.clientReport = clientReport; - } - - @override - Stream> envelopeStream(SentryOptions options) async* { - yield [0]; - } - - @override - SentryEnvelopeHeader get header => SentryEnvelopeHeader( - SentryId.empty(), - SdkVersion(name: 'fixture-name', version: '1'), - ); - - @override - List items = []; - - @override - bool get containsUnhandledException => false; -} diff --git a/dart/test/mocks/mock_environment_variables.dart b/dart/test/mocks/mock_environment_variables.dart deleted file mode 100644 index b7a60a3f31..0000000000 --- a/dart/test/mocks/mock_environment_variables.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:sentry/src/environment/environment_variables.dart'; - -import 'no_such_method_provider.dart'; - -class MockEnvironmentVariables extends EnvironmentVariables - with NoSuchMethodProvider { - MockEnvironmentVariables({ - String? dist, - String? dsn, - String? environment, - String? release, - }) : _dist = dist, - _dsn = dsn, - _environment = environment, - _release = release; - - final String? _dist; - final String? _dsn; - final String? _environment; - final String? _release; - - @override - String? get dist => _dist; - - @override - String? get dsn => _dsn; - - @override - String? get environment => _environment; - - @override - String? get release => _release; -} diff --git a/dart/test/mocks/mock_hub.dart b/dart/test/mocks/mock_hub.dart deleted file mode 100644 index 6b8d530cae..0000000000 --- a/dart/test/mocks/mock_hub.dart +++ /dev/null @@ -1,196 +0,0 @@ -import 'dart:async'; - -import 'package:meta/meta.dart'; -import 'package:sentry/sentry.dart'; - -import '../test_utils.dart'; -import 'mock_sentry_client.dart'; -import 'no_such_method_provider.dart'; - -class MockHub with NoSuchMethodProvider implements Hub { - List captureEventCalls = []; - List captureExceptionCalls = []; - List captureMessageCalls = []; - List addBreadcrumbCalls = []; - List captureLogCalls = []; - List bindClientCalls = []; - - // ignore: deprecated_member_use_from_same_package - List captureTransactionCalls = []; - int closeCalls = 0; - bool _isEnabled = true; - int spanContextCals = 0; - int getSpanCalls = 0; - - final _options = defaultTestOptions(); - - late Scope _scope; - - @override - @internal - SentryOptions get options => _options; - - MockHub() { - _scope = Scope(_options); - } - - /// Useful for tests. - void reset() { - captureEventCalls = []; - captureExceptionCalls = []; - captureMessageCalls = []; - addBreadcrumbCalls = []; - bindClientCalls = []; - closeCalls = 0; - _isEnabled = true; - spanContextCals = 0; - captureTransactionCalls = []; - getSpanCalls = 0; - _scope = Scope(_options); - } - - @override - Future addBreadcrumb(Breadcrumb crumb, {Hint? hint}) async { - addBreadcrumbCalls.add(AddBreadcrumbCall(crumb, hint)); - } - - @override - void bindClient(SentryClient client) { - bindClientCalls.add(client); - } - - @override - Future captureEvent( - SentryEvent event, { - dynamic stackTrace, - Hint? hint, - ScopeCallback? withScope, - }) async { - captureEventCalls.add(CaptureEventCall( - event, - stackTrace, - hint, - )); - return event.eventId; - } - - @override - Future captureException( - dynamic throwable, { - dynamic stackTrace, - Hint? hint, - SentryMessage? message, - ScopeCallback? withScope, - }) async { - captureExceptionCalls.add(CaptureExceptionCall( - throwable, - stackTrace, - hint, - message, - )); - return SentryId.newId(); - } - - @override - Future captureMessage( - String? message, { - SentryLevel? level = SentryLevel.info, - String? template, - List? params, - Hint? hint, - ScopeCallback? withScope, - }) async { - captureMessageCalls.add(CaptureMessageCall( - message, - level, - template, - params, - hint, - )); - return SentryId.newId(); - } - - @override - FutureOr captureLog(SentryLog log) async { - captureLogCalls.add(CaptureLogCall(log, null)); - } - - @override - Future close() async { - closeCalls = closeCalls + 1; - _isEnabled = false; - } - - @override - bool get isEnabled => _isEnabled; - - @override - Future captureTransaction( - SentryTransaction transaction, { - SentryTraceContextHeader? traceContext, - Hint? hint, - }) async { - captureTransactionCalls - .add(CaptureTransactionCall(transaction, traceContext, hint)); - return transaction.eventId; - } - - @override - ISentrySpan? getSpan() { - getSpanCalls++; - return null; - } - - @override - void setSpanContext(throwable, ISentrySpan span, String transaction) { - spanContextCals++; - } - - @override - Scope get scope => _scope; -} - -class CaptureEventCall { - final SentryEvent event; - final dynamic stackTrace; - final Hint? hint; - - CaptureEventCall(this.event, this.stackTrace, this.hint); -} - -class CaptureExceptionCall { - final dynamic throwable; - final dynamic stackTrace; - final Hint? hint; - final SentryMessage? message; - - CaptureExceptionCall( - this.throwable, - this.stackTrace, - this.hint, - this.message, - ); -} - -class CaptureMessageCall { - final String? message; - final SentryLevel? level; - final String? template; - final List? params; - final Hint? hint; - - CaptureMessageCall( - this.message, - this.level, - this.template, - this.params, - this.hint, - ); -} - -class AddBreadcrumbCall { - final Breadcrumb crumb; - final Hint? hint; - - AddBreadcrumbCall(this.crumb, this.hint); -} diff --git a/dart/test/mocks/mock_integration.dart b/dart/test/mocks/mock_integration.dart deleted file mode 100644 index c9945ed6db..0000000000 --- a/dart/test/mocks/mock_integration.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:sentry/sentry.dart'; - -import 'no_such_method_provider.dart'; - -class MockIntegration - with NoSuchMethodProvider - implements Integration { - int closeCalls = 0; - int callCalls = 0; - - @override - void call(Hub hub, SentryOptions options) { - callCalls = callCalls + 1; - } - - @override - void close() { - closeCalls = closeCalls + 1; - } -} diff --git a/dart/test/mocks/mock_log_batcher.dart b/dart/test/mocks/mock_log_batcher.dart deleted file mode 100644 index 9de9a8ae5d..0000000000 --- a/dart/test/mocks/mock_log_batcher.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'dart:async'; - -import 'package:sentry/src/protocol/sentry_log.dart'; -import 'package:sentry/src/sentry_log_batcher.dart'; - -class MockLogBatcher implements SentryLogBatcher { - final addLogCalls = []; - final flushCalls = []; - - @override - FutureOr addLog(SentryLog log) { - addLogCalls.add(log); - } - - @override - Future flush() async { - flushCalls.add(null); - } -} diff --git a/dart/test/mocks/mock_runtime_checker.dart b/dart/test/mocks/mock_runtime_checker.dart deleted file mode 100644 index ae1885963a..0000000000 --- a/dart/test/mocks/mock_runtime_checker.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:sentry/src/runtime_checker.dart'; - -import 'no_such_method_provider.dart'; - -class MockRuntimeChecker extends RuntimeChecker with NoSuchMethodProvider { - MockRuntimeChecker({ - this.isDebug = false, - this.isProfile = false, - this.isRelease = false, - this.isObfuscated = false, - this.isSplitDebugInfo = false, - bool isRootZone = true, - }) : super(isRootZone: isRootZone); - - final bool isDebug; - final bool isProfile; - final bool isRelease; - final bool isObfuscated; - final bool isSplitDebugInfo; - - @override - bool isDebugMode() => isDebug; - - @override - bool isProfileMode() => isProfile; - - @override - bool isReleaseMode() => isRelease; - - @override - bool isAppObfuscated() => isObfuscated; - - @override - bool isSplitDebugInfoBuild() => isSplitDebugInfo; -} diff --git a/dart/test/mocks/mock_scope_observer.dart b/dart/test/mocks/mock_scope_observer.dart deleted file mode 100644 index d0d5d8c048..0000000000 --- a/dart/test/mocks/mock_scope_observer.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:sentry/sentry.dart'; - -class MockScopeObserver extends ScopeObserver { - List addedBreadcrumbs = []; - bool calledAddBreadcrumb = false; - bool calledClearBreadcrumbs = false; - bool calledRemoveContexts = false; - bool calledRemoveExtra = false; - bool calledRemoveTag = false; - bool calledSetContexts = false; - bool calledSetExtra = false; - bool calledSetTag = false; - bool calledSetUser = false; - - int numberOfAddBreadcrumbCalls = 0; - int numberOfClearBreadcrumbsCalls = 0; - int numberOfRemoveContextsCalls = 0; - int numberOfRemoveExtraCalls = 0; - int numberOfRemoveTagCalls = 0; - int numberOfSetContextsCalls = 0; - int numberOfSetExtraCalls = 0; - int numberOfSetTagCalls = 0; - int numberOfSetUserCalls = 0; - - @override - Future addBreadcrumb(Breadcrumb breadcrumb) async { - calledAddBreadcrumb = true; - numberOfAddBreadcrumbCalls += 1; - addedBreadcrumbs.add(breadcrumb); - } - - @override - Future clearBreadcrumbs() async { - calledClearBreadcrumbs = true; - numberOfClearBreadcrumbsCalls += 1; - } - - @override - Future removeContexts(String key) async { - calledRemoveContexts = true; - numberOfRemoveContextsCalls += 1; - } - - @override - Future removeExtra(String key) async { - calledRemoveExtra = true; - numberOfRemoveExtraCalls += 1; - } - - @override - Future removeTag(String key) async { - calledRemoveTag = true; - numberOfRemoveTagCalls += 1; - } - - @override - Future setContexts(String key, value) async { - calledSetContexts = true; - numberOfSetContextsCalls += 1; - } - - @override - Future setExtra(String key, value) async { - calledSetExtra = true; - numberOfSetExtraCalls += 1; - } - - @override - Future setTag(String key, String value) async { - calledSetTag = true; - numberOfSetTagCalls += 1; - } - - @override - Future setUser(SentryUser? user) async { - calledSetUser = true; - numberOfSetUserCalls += 1; - } -} diff --git a/dart/test/mocks/mock_sentry_client.dart b/dart/test/mocks/mock_sentry_client.dart deleted file mode 100644 index 00a61cc203..0000000000 --- a/dart/test/mocks/mock_sentry_client.dart +++ /dev/null @@ -1,188 +0,0 @@ -import 'dart:async'; -import 'package:sentry/sentry.dart'; - -import 'no_such_method_provider.dart'; - -class MockSentryClient with NoSuchMethodProvider implements SentryClient { - List captureEventCalls = []; - List captureExceptionCalls = []; - List captureMessageCalls = []; - List captureEnvelopeCalls = []; - List captureTransactionCalls = []; - List captureFeedbackCalls = []; - List captureLogCalls = []; - int closeCalls = 0; - - @override - Future captureEvent( - SentryEvent event, { - Scope? scope, - dynamic stackTrace, - Hint? hint, - }) async { - captureEventCalls.add(CaptureEventCall( - event, - scope, - stackTrace, - hint, - )); - return event.eventId; - } - - @override - Future captureException( - dynamic throwable, { - dynamic stackTrace, - Scope? scope, - Hint? hint, - }) async { - captureExceptionCalls.add(CaptureExceptionCall( - throwable, - stackTrace, - scope, - hint, - )); - return SentryId.newId(); - } - - @override - Future captureMessage( - String? formatted, { - SentryLevel? level = SentryLevel.info, - String? template, - List? params, - Scope? scope, - Hint? hint, - }) async { - captureMessageCalls.add(CaptureMessageCall( - formatted, - level, - template, - params, - scope, - hint, - )); - return SentryId.newId(); - } - - @override - Future captureEnvelope(SentryEnvelope envelope) async { - captureEnvelopeCalls.add(CaptureEnvelopeCall(envelope)); - return envelope.header.eventId ?? SentryId.newId(); - } - - @override - Future captureFeedback( - SentryFeedback feedback, { - Scope? scope, - Hint? hint, - }) async { - captureFeedbackCalls.add(CaptureFeedbackCall( - feedback, - scope, - hint, - )); - return SentryId.newId(); - } - - @override - FutureOr captureLog(SentryLog log, {Scope? scope}) async { - captureLogCalls.add(CaptureLogCall(log, scope)); - } - - @override - void close() { - closeCalls = closeCalls + 1; - } - - @override - Future captureTransaction( - SentryTransaction transaction, { - Scope? scope, - SentryTraceContextHeader? traceContext, - Hint? hint, - }) async { - captureTransactionCalls - .add(CaptureTransactionCall(transaction, traceContext, hint)); - return transaction.eventId; - } -} - -class CaptureEventCall { - final SentryEvent event; - final Scope? scope; - final dynamic stackTrace; - final Hint? hint; - - CaptureEventCall( - this.event, - this.scope, - this.stackTrace, - this.hint, - ); -} - -class CaptureFeedbackCall { - final SentryFeedback feedback; - final Hint? hint; - final Scope? scope; - - CaptureFeedbackCall( - this.feedback, - this.scope, - this.hint, - ); -} - -class CaptureExceptionCall { - final dynamic throwable; - final dynamic stackTrace; - final Scope? scope; - final Hint? hint; - - CaptureExceptionCall( - this.throwable, - this.stackTrace, - this.scope, - this.hint, - ); -} - -class CaptureMessageCall { - final String? formatted; - final SentryLevel? level; - final String? template; - final List? params; - final Scope? scope; - final Hint? hint; - - CaptureMessageCall( - this.formatted, - this.level, - this.template, - this.params, - this.scope, - this.hint, - ); -} - -class CaptureEnvelopeCall { - final SentryEnvelope envelope; - - CaptureEnvelopeCall(this.envelope); -} - -class CaptureTransactionCall { - final SentryTransaction transaction; - final SentryTraceContextHeader? traceContext; - final Hint? hint; - - CaptureTransactionCall(this.transaction, this.traceContext, this.hint); -} - -class CaptureLogCall { - final SentryLog log; - final Scope? scope; - - CaptureLogCall(this.log, this.scope); -} diff --git a/dart/test/mocks/mock_transport.dart b/dart/test/mocks/mock_transport.dart deleted file mode 100644 index f9ba5b4829..0000000000 --- a/dart/test/mocks/mock_transport.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:convert'; - -import 'package:sentry/sentry.dart'; -import 'package:test/expect.dart'; - -class MockTransport implements Transport { - List envelopes = []; - List events = []; - List statsdItems = []; - List> logs = []; - - int _calls = 0; - String _exceptions = ''; - - int get calls { - expect(_exceptions, isEmpty); - return _calls; - } - - bool parseFromEnvelope = true; - - bool called(int calls) { - return _calls == calls; - } - - @override - Future send(SentryEnvelope envelope) async { - _calls++; - - // Exception here would be swallowed by Sentry, making it hard to find test - // failure causes. Instead, we log them and check on access to [calls]. - try { - envelopes.add(envelope); - if (parseFromEnvelope) { - await _parseEnvelope(envelope); - } - } catch (e, stack) { - _exceptions += '$e\n$stack\n\n'; - rethrow; - } - - return envelope.header.eventId ?? SentryId.empty(); - } - - Future _parseEnvelope(SentryEnvelope envelope) async { - final RegExp statSdRegex = RegExp('^(?!{).+@.+:.+\\|.+', multiLine: true); - - final envelopeItemData = await envelope.items.first.dataFactory(); - final envelopeItem = utf8.decode(envelopeItemData); - - if (statSdRegex.hasMatch(envelopeItem)) { - statsdItems.add(envelopeItem); - } else if (envelopeItem.contains('items') && - envelopeItem.contains('timestamp') && - envelopeItem.contains('trace_id') && - envelopeItem.contains('level') && - envelopeItem.contains('body')) { - final envelopeItemJson = jsonDecode(envelopeItem) as Map; - logs.add(envelopeItemJson); - } else { - final envelopeItemJson = jsonDecode(envelopeItem) as Map; - events.add(SentryEvent.fromJson(envelopeItemJson)); - } - } - - void reset() { - envelopes.clear(); - events.clear(); - _calls = 0; - } -} - -class ThrowingTransport implements Transport { - @override - Future send(SentryEnvelope envelope) async { - throw Exception('foo bar'); - } -} diff --git a/dart/test/mocks/no_such_method_provider.dart b/dart/test/mocks/no_such_method_provider.dart deleted file mode 100644 index 64253e9651..0000000000 --- a/dart/test/mocks/no_such_method_provider.dart +++ /dev/null @@ -1,7 +0,0 @@ -mixin NoSuchMethodProvider { - @override - void noSuchMethod(Invocation invocation) { - 'Method ${invocation.memberName} was called ' - 'with arguments ${invocation.positionalArguments}'; - } -} diff --git a/dart/test/propagation_context_test.dart b/dart/test/propagation_context_test.dart deleted file mode 100644 index 22347faa3a..0000000000 --- a/dart/test/propagation_context_test.dart +++ /dev/null @@ -1,157 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:test/test.dart'; - -import 'test_utils.dart'; - -void main() { - group('PropagationContext', () { - group('traceId', () { - test('is a new trace id by default', () { - final hub = Hub(defaultTestOptions()); - final sut = hub.scope.propagationContext; - final traceId = sut.traceId; - expect(traceId, isNotNull); - }); - - test('is reused for transactions within the same trace', () { - final options = defaultTestOptions()..tracesSampleRate = 1.0; - final hub = Hub(options); - final sut = hub.scope.propagationContext; - - final tx1 = hub.startTransaction('tx1', 'op') as SentryTracer; - final traceId1 = sut.traceId; - - final tx2 = hub.startTransaction('tx2', 'op') as SentryTracer; - final traceId2 = sut.traceId; - - expect(tx1.context.traceId, equals(tx2.context.traceId)); - expect(tx1.context.traceId, equals(traceId1)); - expect(traceId1, equals(traceId2)); - }); - }); - - group('sampleRand', () { - test('is null by default', () { - final hub = Hub(defaultTestOptions()); - final sut = hub.scope.propagationContext; - final sampleRand = sut.sampleRand; - expect(sampleRand, isNull); - }); - - test('is set by the first transaction and stays unchanged', () { - final options = defaultTestOptions()..tracesSampleRate = 1.0; - final hub = Hub(options); - final sut = hub.scope.propagationContext; - - final tx1 = hub.startTransaction('tx1', 'op') as SentryTracer; - final rand1 = tx1.samplingDecision?.sampleRand; - expect(rand1, isNotNull); - - final tx2 = hub.startTransaction('tx2', 'op') as SentryTracer; - final rand2 = tx2.samplingDecision?.sampleRand; - - expect(rand2, equals(rand1)); - expect(rand1, equals(sut.sampleRand)); - }); - }); - - group('sampled', () { - test('is null by default', () { - final hub = Hub(defaultTestOptions()..tracesSampleRate = 1.0); - final sut = hub.scope.propagationContext; - expect(sut.sampled, isNull); - }); - - test('is set by the first transaction and stays unchanged', () { - final hub = Hub(defaultTestOptions()..tracesSampleRate = 1.0); - final sut = hub.scope.propagationContext; - // 1. Start the first (root) transaction with an explicit sampled = true. - final txContextTrue = SentryTransactionContext( - 'trx', - 'op', - samplingDecision: SentryTracesSamplingDecision(true), - ); - hub.startTransactionWithContext(txContextTrue); - - expect(sut.sampled, isTrue); - - // 2. Start a second transaction with sampled = false – the flag must not change. - final txContextFalse = SentryTransactionContext( - 'trx-2', - 'op', - samplingDecision: SentryTracesSamplingDecision(false), - ); - hub.startTransactionWithContext(txContextFalse); - - expect(sut.sampled, isTrue, - reason: 'sampled flag must remain unchanged for the trace'); - }); - - test('is reset when a new trace is generated', () { - final hub = Hub(defaultTestOptions()..tracesSampleRate = 1.0); - final sut = hub.scope.propagationContext; - final txContext = SentryTransactionContext( - 'trx', - 'op', - samplingDecision: SentryTracesSamplingDecision(true), - ); - hub.startTransactionWithContext(txContext); - expect(sut.sampled, isTrue); - - // Simulate new trace. - hub.generateNewTrace(); - expect(sut.sampled, isNull); - }); - - test('applySamplingDecision only sets sampled flag once', () { - final hub = Hub(defaultTestOptions()..tracesSampleRate = 1.0); - final sut = hub.scope.propagationContext; - - expect(sut.sampled, isNull); - sut.applySamplingDecision(true); - expect(sut.sampled, isTrue); - sut.applySamplingDecision(false); - expect(sut.sampled, isTrue); - }); - }); - - group('resetTrace', () { - test('resets values', () { - final hub = Hub(defaultTestOptions()..tracesSampleRate = 1.0); - final sut = hub.scope.propagationContext; - - final traceId = SentryId.newId(); - sut.traceId = traceId; - sut.sampleRand = 1.0; - sut.applySamplingDecision(true); - - sut.resetTrace(); - - expect(sut.traceId, isNot(traceId)); - expect(sut.sampleRand, isNull); - expect(sut.sampled, isNull); - }); - }); - - group('toSentryTrace', () { - test('header reflects values', () { - final options = defaultTestOptions()..tracesSampleRate = 1.0; - final hub = Hub(options); - final sut = hub.scope.propagationContext; - - final txContext = SentryTransactionContext( - 'trx', - 'op', - samplingDecision: SentryTracesSamplingDecision(true), - ); - hub.startTransactionWithContext(txContext); - - final header = sut.toSentryTrace(); - expect(header.sampled, isTrue); - expect(header.value.split('-').length, 3, - reason: 'header must contain the sampled decision'); - }); - }); - }); -} diff --git a/dart/test/protocol/access_aware_map_tests.dart b/dart/test/protocol/access_aware_map_tests.dart deleted file mode 100644 index b9c08f2b9a..0000000000 --- a/dart/test/protocol/access_aware_map_tests.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/src/protocol/access_aware_map.dart'; -import 'package:test/test.dart'; - -void main() { - group('MapBase', () { - test('set/get value for key', () { - final sut = AccessAwareMap({ - 'foo': 'foo', - }); - - sut['foo'] = 'bar'; - sut['bar'] = 'foo'; - - expect(sut['foo'], 'bar'); - expect(sut['bar'], 'foo'); - }); - - test('clear', () { - final sut = AccessAwareMap({ - 'foo': 'foo', - }); - - sut.clear(); - - expect(sut.isEmpty, true); - }); - - test('keys', () { - final sut = AccessAwareMap({ - 'foo': 'foo', - 'bar': 'bar', - }); - expect( - sut.keys.sortedBy((it) => it), ['bar', 'foo'].sortedBy((it) => it)); - }); - - test('remove', () { - final sut = AccessAwareMap({ - 'foo': 'foo', - }); - - sut.remove('foo'); - - expect(sut.isEmpty, true); - }); - }); - - group('access aware', () { - test('collects accessedKeys', () { - final sut = AccessAwareMap({ - 'foo': 'foo', - 'bar': 'bar', - }); - - sut['foo']; - sut['bar']; - sut['baz']; - - expect(sut.accessedKeysWithValues, {'foo', 'bar', 'baz'}); - }); - - test('returns notAccessed data', () { - final sut = AccessAwareMap({ - 'foo': 'foo', - 'bar': 'bar', - }); - - sut['foo']; - - final notAccessed = sut.notAccessed(); - expect(notAccessed, isNotNull); - expect(notAccessed?.containsKey('foo'), false); - expect(notAccessed?.containsKey('bar'), true); - }); - }); - - group('map base functionality', () { - test('set value with []= operator', () { - final sut = AccessAwareMap({ - 'foo': 'foo', - }); - - sut['foo'] = 'bar'; - sut['bar'] = 'foo'; - - expect(sut['foo'], 'bar'); - expect(sut['bar'], 'foo'); - }); - - test('clear', () { - final sut = AccessAwareMap({ - 'foo': 'foo', - }); - - sut.clear(); - - expect(sut.accessedKeysWithValues.isEmpty, true); - expect(sut.isEmpty, true); - }); - - test('keys', () { - final sut = AccessAwareMap({ - 'foo': 'foo', - 'bar': 'bar', - }); - expect(sut.keys.toSet(), {'foo', 'bar'}); - }); - - test('remove', () { - final sut = AccessAwareMap({'foo': 'foo'}); - - sut.remove('foo'); - - expect(sut['foo'], isNull); - }); - }); -} diff --git a/dart/test/protocol/breadcrumb_test.dart b/dart/test/protocol/breadcrumb_test.dart deleted file mode 100644 index f1f85f2ec8..0000000000 --- a/dart/test/protocol/breadcrumb_test.dart +++ /dev/null @@ -1,229 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final timestamp = DateTime.now(); - - final breadcrumb = Breadcrumb( - message: 'message', - timestamp: timestamp, - data: {'key': 'value'}, - level: SentryLevel.warning, - category: 'category', - type: 'type', - unknown: testUnknown, - ); - - final breadcrumbJson = { - 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), - 'message': 'message', - 'category': 'category', - 'data': {'key': 'value'}, - 'level': 'warning', - 'type': 'type', - }; - breadcrumbJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = breadcrumb.toJson(); - - expect( - DeepCollectionEquality().equals(breadcrumbJson, json), - true, - ); - }); - - test('fromJson', () { - final breadcrumb = Breadcrumb.fromJson(breadcrumbJson); - final json = breadcrumb.toJson(); - - expect( - DeepCollectionEquality().equals(breadcrumbJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = breadcrumb; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - test('copyWith takes new values', () { - final data = breadcrumb; - - final timestamp = DateTime.now(); - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - message: 'message1', - timestamp: timestamp, - data: {'key1': 'value1'}, - level: SentryLevel.fatal, - category: 'category1', - type: 'type1', - ); - - expect('message1', copy.message); - expect(timestamp, copy.timestamp); - expect({'key1': 'value1'}, copy.data); - expect(SentryLevel.fatal, copy.level); - expect('category1', copy.category); - expect('type1', copy.type); - }); - }); - - group('ctor', () { - test('Breadcrumb http', () { - final breadcrumb = Breadcrumb.http( - url: Uri.parse('https://example.org'), - method: 'GET', - level: SentryLevel.fatal, - reason: 'OK', - statusCode: 200, - requestDuration: Duration(milliseconds: 55), - timestamp: DateTime.now(), - requestBodySize: 2, - responseBodySize: 3, - httpQuery: 'foo=bar', - httpFragment: 'baz'); - final json = breadcrumb.toJson(); - - expect(json, { - 'timestamp': - formatDateAsIso8601WithMillisPrecision(breadcrumb.timestamp), - 'category': 'http', - 'data': { - 'url': 'https://example.org', - 'method': 'GET', - 'status_code': 200, - 'reason': 'OK', - 'duration': '0:00:00.055000', - 'request_body_size': 2, - 'response_body_size': 3, - 'http.query': 'foo=bar', - 'http.fragment': 'baz', - 'start_timestamp': breadcrumb.timestamp.millisecondsSinceEpoch - 55, - 'end_timestamp': breadcrumb.timestamp.millisecondsSinceEpoch - }, - 'level': 'fatal', - 'type': 'http', - }); - }); - - test('Breadcrumb http', () { - final breadcrumb = Breadcrumb.http( - url: Uri.parse('https://example.org'), - method: 'GET', - requestDuration: Duration(milliseconds: 10), - ); - final json = breadcrumb.toJson(); - - expect(json, { - 'timestamp': - formatDateAsIso8601WithMillisPrecision(breadcrumb.timestamp), - 'category': 'http', - 'data': { - 'url': 'https://example.org', - 'method': 'GET', - 'duration': '0:00:00.010000', - 'start_timestamp': breadcrumb.timestamp.millisecondsSinceEpoch - 10, - 'end_timestamp': breadcrumb.timestamp.millisecondsSinceEpoch - }, - 'level': 'info', - 'type': 'http', - }); - }); - - test('Minimal Breadcrumb http', () { - final breadcrumb = Breadcrumb.http( - url: Uri.parse('https://example.org'), - method: 'GET', - ); - final json = breadcrumb.toJson(); - - expect(json, { - 'timestamp': - formatDateAsIso8601WithMillisPrecision(breadcrumb.timestamp), - 'category': 'http', - 'data': { - 'url': 'https://example.org', - 'method': 'GET', - }, - 'level': 'info', - 'type': 'http', - }); - }); - - test('Breadcrumb console', () { - final breadcrumb = Breadcrumb.console( - message: 'Foo Bar', - ); - final json = breadcrumb.toJson(); - - expect(json, { - 'message': 'Foo Bar', - 'timestamp': - formatDateAsIso8601WithMillisPrecision(breadcrumb.timestamp), - 'category': 'console', - 'type': 'debug', - 'level': 'info', - }); - }); - - test('extensive Breadcrumb console', () { - final breadcrumb = Breadcrumb.console( - message: 'Foo Bar', - level: SentryLevel.error, - data: {'foo': 'bar'}, - ); - final json = breadcrumb.toJson(); - - expect(json, { - 'message': 'Foo Bar', - 'timestamp': - formatDateAsIso8601WithMillisPrecision(breadcrumb.timestamp), - 'category': 'console', - 'type': 'debug', - 'level': 'error', - 'data': {'foo': 'bar'}, - }); - }); - - test('extensive Breadcrumb user interaction', () { - final time = DateTime.now().toUtc(); - final breadcrumb = Breadcrumb.userInteraction( - message: 'Foo Bar', - level: SentryLevel.error, - timestamp: time, - data: {'foo': 'bar'}, - subCategory: 'click', - viewId: 'foo', - viewClass: 'bar', - ); - final json = breadcrumb.toJson(); - - expect(json, { - 'message': 'Foo Bar', - 'timestamp': formatDateAsIso8601WithMillisPrecision(time), - 'category': 'ui.click', - 'type': 'user', - 'level': 'error', - 'data': { - 'foo': 'bar', - 'view.id': 'foo', - 'view.class': 'bar', - }, - }); - }); - }); -} diff --git a/dart/test/protocol/contexts_test.dart b/dart/test/protocol/contexts_test.dart deleted file mode 100644 index 39a9fe7700..0000000000 --- a/dart/test/protocol/contexts_test.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - final _traceId = SentryId.fromId('1988bb1b6f0d4c509e232f0cb9aaeaea'); - final _spanId = SpanId.fromId('976e0cd945864f60'); - final _parentSpanId = SpanId.fromId('c9c9fc3f9d4346df'); - final _associatedEventId = - SentryId.fromId('8a32c0f9be1d34a5efb2c4a10d80de9a'); - - final _trace = SentryTraceContext( - traceId: _traceId, - spanId: _spanId, - operation: 'op', - parentSpanId: _parentSpanId, - sampled: true, - description: 'desc', - status: SpanStatus.ok(), - ); - - final _feedback = SentryFeedback( - message: 'fixture-message', - contactEmail: 'fixture-contactEmail', - name: 'fixture-name', - replayId: 'fixture-replayId', - url: "https://fixture-url.com", - associatedEventId: _associatedEventId, - ); - - final _contexts = Contexts( - device: SentryDevice(batteryLevel: 90.0), - operatingSystem: SentryOperatingSystem(name: 'name'), - runtimes: [SentryRuntime(name: 'name')], - app: SentryApp(name: 'name'), - browser: SentryBrowser(name: 'name'), - gpu: SentryGpu(id: 1), - culture: SentryCulture(locale: 'foo-bar'), - trace: _trace, - feedback: _feedback, - flags: SentryFeatureFlags(values: [ - SentryFeatureFlag(flag: 'name', result: true), - ]), - ); - - final _contextsJson = { - 'device': {'battery_level': 90.0}, - 'os': {'name': 'name'}, - 'runtime': {'name': 'name'}, - 'app': {'app_name': 'name'}, - 'browser': {'name': 'name'}, - 'gpu': {'id': 1}, - 'culture': {'locale': 'foo-bar'}, - 'trace': { - 'span_id': '976e0cd945864f60', - 'trace_id': '1988bb1b6f0d4c509e232f0cb9aaeaea', - 'op': 'op', - 'parent_span_id': 'c9c9fc3f9d4346df', - 'description': 'desc', - 'status': 'ok' - }, - 'feedback': { - 'message': 'fixture-message', - 'contact_email': 'fixture-contactEmail', - 'name': 'fixture-name', - 'replay_id': 'fixture-replayId', - 'url': 'https://fixture-url.com', - 'associated_event_id': '8a32c0f9be1d34a5efb2c4a10d80de9a', - }, - 'flags': { - 'values': [ - {'flag': 'name', 'result': true} - ], - }, - }; - - final _contextsMutlipleRuntimes = Contexts( - runtimes: [ - SentryRuntime(name: 'name'), - SentryRuntime(name: 'name'), - SentryRuntime(key: 'key') - ], - ); - - final _contextsMutlipleRuntimesJson = { - 'name': {'name': 'name', 'type': 'runtime'}, - 'name0': {'name': 'name', 'type': 'runtime'}, - }; - - group('json', () { - test('toJson', () { - final json = _contexts.toJson(); - - expect( - DeepCollectionEquality().equals(_contextsJson, json), - true, - ); - }); - test('toJson multiple runtimes', () { - final json = _contextsMutlipleRuntimes.toJson(); - - expect( - DeepCollectionEquality().equals(_contextsMutlipleRuntimesJson, json), - true, - ); - }); - test('fromJson', () { - final contexts = Contexts.fromJson(_contextsJson); - final json = contexts.toJson(); - - expect( - DeepCollectionEquality().equals(_contextsJson, json), - true, - ); - }); - test('fromJson multiple runtimes', () { - final contextsMutlipleRuntimes = - Contexts.fromJson(_contextsMutlipleRuntimesJson); - final json = contextsMutlipleRuntimes.toJson(); - - expect( - DeepCollectionEquality().equals(_contextsMutlipleRuntimesJson, json), - true, - ); - }); - }); -} diff --git a/dart/test/protocol/culture_test.dart b/dart/test/protocol/culture_test.dart deleted file mode 100644 index 681af25d9d..0000000000 --- a/dart/test/protocol/culture_test.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; -import 'package:collection/collection.dart'; - -void main() { - group(SentryCulture, () { - test('copyWith keeps unchanged', () { - final data = _generate(); - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = _generate(); - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - calendar: 'calendar', - displayName: 'displayName', - is24HourFormat: false, // opposite of the value from _generate - locale: 'locale', - timezone: 'timezone', - ); - - expect('calendar', copy.calendar); - expect('displayName', copy.displayName); - expect(false, copy.is24HourFormat); - expect('locale', copy.locale); - expect('timezone', copy.timezone); - }); - test('toJson', () { - final data = _generate(); - - expect(data.toJson(), { - 'calendar': 'FooCalendar', - 'display_name': 'FooLanguage', - 'is_24_hour_format': true, - 'locale': 'fo-ba', - 'timezone': 'best-timezone', - }); - }); - }); -} - -SentryCulture _generate() => SentryCulture( - calendar: 'FooCalendar', - displayName: 'FooLanguage', - is24HourFormat: true, - locale: 'fo-ba', - timezone: 'best-timezone', - ); diff --git a/dart/test/protocol/debug_image_test.dart b/dart/test/protocol/debug_image_test.dart deleted file mode 100644 index 87dddaaffe..0000000000 --- a/dart/test/protocol/debug_image_test.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final debugImage = DebugImage( - type: 'type', - imageAddr: 'imageAddr', - debugId: 'debugId', - debugFile: 'debugFile', - imageSize: 1, - uuid: 'uuid', - codeFile: 'codeFile', - arch: 'arch', - codeId: 'codeId', - unknown: testUnknown, - ); - - final debugImageJson = { - 'uuid': 'uuid', - 'type': 'type', - 'debug_id': 'debugId', - 'debug_file': 'debugFile', - 'code_file': 'codeFile', - 'image_addr': 'imageAddr', - 'image_size': 1, - 'arch': 'arch', - 'code_id': 'codeId', - }; - debugImageJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = debugImage.toJson(); - - expect( - MapEquality().equals(debugImageJson, json), - true, - ); - }); - test('fromJson', () { - final debugImage = DebugImage.fromJson(debugImageJson); - final json = debugImage.toJson(); - - expect( - MapEquality().equals(debugImageJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = debugImage; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = debugImage; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - type: 'type1', - name: 'name', - imageAddr: 'imageAddr1', - imageVmAddr: 'imageVmAddr1', - debugId: 'debugId1', - debugFile: 'debugFile1', - imageSize: 2, - uuid: 'uuid1', - codeFile: 'codeFile1', - arch: 'arch1', - codeId: 'codeId1', - ); - - expect('type1', copy.type); - expect('imageAddr1', copy.imageAddr); - expect('debugId1', copy.debugId); - expect('debugFile1', copy.debugFile); - expect(2, copy.imageSize); - expect('uuid1', copy.uuid); - expect('codeFile1', copy.codeFile); - expect('arch1', copy.arch); - expect('codeId1', copy.codeId); - }); - }); -} diff --git a/dart/test/protocol/debug_meta_test.dart b/dart/test/protocol/debug_meta_test.dart deleted file mode 100644 index 0a70307369..0000000000 --- a/dart/test/protocol/debug_meta_test.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final debugMeta = DebugMeta( - sdk: SdkInfo( - sdkName: 'sdkName', - ), - images: [DebugImage(type: 'macho', uuid: 'uuid')], - unknown: testUnknown, - ); - - final debugMetaJson = { - 'sdk_info': {'sdk_name': 'sdkName'}, - 'images': [ - {'uuid': 'uuid', 'type': 'macho'} - ] - }; - debugMetaJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = debugMeta.toJson(); - - expect( - DeepCollectionEquality().equals(debugMetaJson, json), - true, - ); - }); - test('fromJson', () { - final debugMeta = DebugMeta.fromJson(debugMetaJson); - final json = debugMeta.toJson(); - - expect( - DeepCollectionEquality().equals(debugMetaJson, json), - true, - ); - }); - }); -} diff --git a/dart/test/protocol/mechanism_test.dart b/dart/test/protocol/mechanism_test.dart deleted file mode 100644 index e8675d9658..0000000000 --- a/dart/test/protocol/mechanism_test.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final mechanism = Mechanism( - type: 'type', - description: 'description', - helpLink: 'helpLink', - handled: true, - synthetic: true, - meta: {'key': 'value'}, - data: {'keyb': 'valueb'}, - isExceptionGroup: false, - exceptionId: 0, - parentId: 0, - source: 'source', - unknown: testUnknown, - ); - - final mechanismJson = { - 'type': 'type', - 'description': 'description', - 'help_link': 'helpLink', - 'handled': true, - 'meta': {'key': 'value'}, - 'data': {'keyb': 'valueb'}, - 'synthetic': true, - 'is_exception_group': false, - 'source': 'source', - 'exception_id': 0, - 'parent_id': 0, - }; - mechanismJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = mechanism.toJson(); - - expect( - DeepCollectionEquality().equals(mechanismJson, json), - true, - ); - }); - test('fromJson', () { - final mechanism = Mechanism.fromJson(mechanismJson); - final json = mechanism.toJson(); - - expect( - DeepCollectionEquality().equals(mechanismJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = mechanism; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect(data.toJson(), copy.toJson()); - }); - - test('copyWith takes new values', () { - final data = mechanism; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - type: 'type1', - description: 'description1', - helpLink: 'helpLink1', - handled: false, - synthetic: false, - meta: {'key1': 'value1'}, - data: {'keyb1': 'valueb1'}, - exceptionId: 1, - parentId: 1, - isExceptionGroup: false, - source: 'foo', - ); - - expect('type1', copy.type); - expect('description1', copy.description); - expect('helpLink1', copy.helpLink); - expect(false, copy.handled); - expect(false, copy.synthetic); - expect({'key1': 'value1'}, copy.meta); - expect({'keyb1': 'valueb1'}, copy.data); - expect(1, copy.exceptionId); - expect(1, copy.parentId); - expect(false, copy.isExceptionGroup); - expect('foo', copy.source); - }); - }); -} diff --git a/dart/test/protocol/rate_limit_parser_test.dart b/dart/test/protocol/rate_limit_parser_test.dart deleted file mode 100644 index 567dec34f0..0000000000 --- a/dart/test/protocol/rate_limit_parser_test.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'package:sentry/src/transport/rate_limit_parser.dart'; -import 'package:sentry/src/transport/data_category.dart'; -import 'package:test/test.dart'; - -void main() { - group('parseRateLimitHeader', () { - test('single rate limit with single category', () { - final sut = RateLimitParser('50:transaction').parseRateLimitHeader(); - - expect(sut.length, 1); - expect(sut[0].category, DataCategory.transaction); - expect(sut[0].duration.inMilliseconds, 50000); - }); - - test('single rate limit with multiple categories', () { - final sut = - RateLimitParser('50:transaction;session').parseRateLimitHeader(); - - expect(sut.length, 2); - expect(sut[0].category, DataCategory.transaction); - expect(sut[0].duration.inMilliseconds, 50000); - expect(sut[1].category, DataCategory.session); - expect(sut[1].duration.inMilliseconds, 50000); - }); - - test('don`t apply rate limit for unknown categories ', () { - final sut = RateLimitParser('50:somethingunknown').parseRateLimitHeader(); - - expect(sut.length, 0); - }); - - test('apply all if there are no categories', () { - final sut = RateLimitParser('50::key').parseRateLimitHeader(); - - expect(sut.length, 1); - expect(sut[0].category, DataCategory.all); - expect(sut[0].duration.inMilliseconds, 50000); - }); - - test('multiple rate limits', () { - final sut = - RateLimitParser('50:transaction, 70:session').parseRateLimitHeader(); - - expect(sut.length, 2); - expect(sut[0].category, DataCategory.transaction); - expect(sut[0].duration.inMilliseconds, 50000); - expect(sut[1].category, DataCategory.session); - expect(sut[1].duration.inMilliseconds, 70000); - }); - - test('multiple rate limits with same category', () { - final sut = RateLimitParser('50:transaction, 70:transaction') - .parseRateLimitHeader(); - - expect(sut.length, 2); - expect(sut[0].category, DataCategory.transaction); - expect(sut[0].duration.inMilliseconds, 50000); - expect(sut[1].category, DataCategory.transaction); - expect(sut[1].duration.inMilliseconds, 70000); - }); - - test('ignore case', () { - final sut = RateLimitParser('50:TRANSACTION').parseRateLimitHeader(); - - expect(sut.length, 1); - expect(sut[0].category, DataCategory.transaction); - expect(sut[0].duration.inMilliseconds, 50000); - }); - - test('un-parseable returns default duration', () { - final sut = RateLimitParser('foobar:transaction').parseRateLimitHeader(); - - expect(sut.length, 1); - expect(sut[0].category, DataCategory.transaction); - expect(sut[0].duration.inMilliseconds, - RateLimitParser.httpRetryAfterDefaultDelay.inMilliseconds); - }); - - test('do not parse namespaces if not metric_bucket', () { - final sut = - RateLimitParser('1:transaction:organization:quota_exceeded:custom') - .parseRateLimitHeader(); - - expect(sut.length, 1); - expect(sut[0].category, DataCategory.transaction); - expect(sut[0].namespaces, isEmpty); - }); - - test('parse namespaces on metric_bucket', () { - final sut = - RateLimitParser('1:metric_bucket:organization:quota_exceeded:custom') - .parseRateLimitHeader(); - - expect(sut.length, 1); - expect(sut[0].category, DataCategory.metricBucket); - expect(sut[0].namespaces, isNotEmpty); - expect(sut[0].namespaces.first, 'custom'); - }); - - test('parse empty namespaces on metric_bucket', () { - final sut = - RateLimitParser('1:metric_bucket:organization:quota_exceeded:') - .parseRateLimitHeader(); - - expect(sut.length, 1); - expect(sut[0].category, DataCategory.metricBucket); - expect(sut[0].namespaces, isEmpty); - }); - - test('parse missing namespaces on metric_bucket', () { - final sut = RateLimitParser('1:metric_bucket').parseRateLimitHeader(); - - expect(sut.length, 1); - expect(sut[0].category, DataCategory.metricBucket); - expect(sut[0].namespaces, isEmpty); - }); - }); - - group('parseRetryAfterHeader', () { - test('null returns default category all with default duration', () { - final sut = RateLimitParser(null).parseRetryAfterHeader(); - - expect(sut.length, 1); - expect(sut[0].category, DataCategory.all); - expect(sut[0].duration.inMilliseconds, - RateLimitParser.httpRetryAfterDefaultDelay.inMilliseconds); - }); - - test('parseable returns default category with duration in millis', () { - final sut = RateLimitParser('8').parseRetryAfterHeader(); - - expect(sut.length, 1); - expect(sut[0].category, DataCategory.all); - expect(sut[0].duration.inMilliseconds, 8000); - }); - - test('un-parseable returns default category with default duration', () { - final sut = RateLimitParser('foobar').parseRetryAfterHeader(); - - expect(sut.length, 1); - expect(sut[0].category, DataCategory.all); - expect(sut[0].duration.inMilliseconds, - RateLimitParser.httpRetryAfterDefaultDelay.inMilliseconds); - }); - }); -} diff --git a/dart/test/protocol/rate_limiter_test.dart b/dart/test/protocol/rate_limiter_test.dart deleted file mode 100644 index 9a6079810b..0000000000 --- a/dart/test/protocol/rate_limiter_test.dart +++ /dev/null @@ -1,350 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/client_reports/discard_reason.dart'; -import 'package:sentry/src/sentry_envelope_header.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:sentry/src/transport/data_category.dart'; -import 'package:sentry/src/transport/rate_limiter.dart'; -import 'package:test/test.dart'; - -import '../mocks/mock_client_report_recorder.dart'; -import '../mocks/mock_hub.dart'; -import '../test_utils.dart'; - -void main() { - var fixture = Fixture(); - - setUp(() { - fixture = Fixture(); - }); - - test('uses X-Sentry-Rate-Limit and allows sending if time has passed', () { - final rateLimiter = fixture.getSut(); - fixture.dateTimeToReturn = 0; - - final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits( - '50:transaction:key, 1:default;error;security:organization', null, 1); - - fixture.dateTimeToReturn = 1001; - - final result = rateLimiter.filter(envelope); - expect(result, isNotNull); - expect(result!.items.length, 1); - }); - - test( - 'parse X-Sentry-Rate-Limit and set its values and retry after should be true', - () { - final rateLimiter = fixture.getSut(); - fixture.dateTimeToReturn = 0; - final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - - final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits( - '50:transaction:key, 2700:default;error;security:organization', - null, - 1); - - final result = rateLimiter.filter(envelope); - expect(result, isNull); - }); - - test( - 'parse X-Sentry-Rate-Limit and set its values and retry after should be false', - () { - final rateLimiter = fixture.getSut(); - fixture.dateTimeToReturn = 0; - final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - - final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits( - '1:transaction:key, 1:default;error;security:organization', null, 1); - - fixture.dateTimeToReturn = 1001; - - final result = rateLimiter.filter(envelope); - expect(result, isNotNull); - expect(1, result!.items.length); - }); - - test( - 'When X-Sentry-Rate-Limit categories are empty, applies to all the categories', - () { - final rateLimiter = fixture.getSut(); - fixture.dateTimeToReturn = 0; - final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits('50::key', null, 1); - - final result = rateLimiter.filter(envelope); - expect(result, isNull); - }); - - test( - 'When all categories is set but expired, applies only for specific category', - () { - final rateLimiter = fixture.getSut(); - fixture.dateTimeToReturn = 0; - final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits( - '1::key, 60:default;error;security:organization', null, 1); - - fixture.dateTimeToReturn = 1001; - - final result = rateLimiter.filter(envelope); - expect(result, isNull); - }); - - test('When category has shorter rate limiting, do not apply new timestamp', - () { - final rateLimiter = fixture.getSut(); - fixture.dateTimeToReturn = 0; - final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits( - '60:error:key, 1:error:organization', null, 1); - - fixture.dateTimeToReturn = 1001; - - final result = rateLimiter.filter(envelope); - expect(result, isNull); - }); - - test('When category has longer rate limiting, apply new timestamp', () { - final rateLimiter = fixture.getSut(); - fixture.dateTimeToReturn = 0; - final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits( - '1:error:key, 5:error:organization', null, 1); - - fixture.dateTimeToReturn = 1001; - - final result = rateLimiter.filter(envelope); - expect(result, isNull); - }); - - test('When both retry headers are not present, default delay is set', () { - final rateLimiter = fixture.getSut(); - fixture.dateTimeToReturn = 0; - final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits(null, null, 429); - - fixture.dateTimeToReturn = 1001; - - final result = rateLimiter.filter(envelope); - expect(result, isNull); - }); - - test( - 'When no sentryRateLimitHeader available, it fallback to retryAfterHeader', - () { - final rateLimiter = fixture.getSut(); - fixture.dateTimeToReturn = 0; - final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits(null, '50', 429); - - fixture.dateTimeToReturn = 1001; - - final result = rateLimiter.filter(envelope); - expect(result, isNull); - }); - - test('dropping of event recorded', () { - final rateLimiter = fixture.getSut(); - - final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final eventEnvelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits( - '1:error:key, 5:error:organization', null, 1); - - final result = rateLimiter.filter(eventEnvelope); - expect(result, isNull); - - expect(fixture.mockRecorder.discardedEvents.first.category, - DataCategory.error); - expect(fixture.mockRecorder.discardedEvents.first.reason, - DiscardReason.rateLimitBackoff); - }); - - test('dropping of transaction recorded', () { - final rateLimiter = fixture.getSut(); - - final transaction = fixture.getTransaction(); - transaction.tracer.startChild('child1'); - transaction.tracer.startChild('child2'); - final eventItem = SentryEnvelopeItem.fromTransaction(transaction); - final eventEnvelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits( - '1:transaction:key, 5:transaction:organization', null, 1); - - final result = rateLimiter.filter(eventEnvelope); - expect(result, isNull); - - expect(fixture.mockRecorder.discardedEvents.length, 2); - - final transactionDiscardedEvent = fixture.mockRecorder.discardedEvents - .firstWhereOrNull((element) => - element.category == DataCategory.transaction && - element.reason == DiscardReason.rateLimitBackoff); - - final spanDiscardedEvent = fixture.mockRecorder.discardedEvents - .firstWhereOrNull((element) => - element.category == DataCategory.span && - element.reason == DiscardReason.rateLimitBackoff); - - expect(transactionDiscardedEvent, isNotNull); - expect(spanDiscardedEvent, isNotNull); - expect(spanDiscardedEvent!.quantity, 3); - }); - - group('apply rateLimit', () { - test('error', () { - final rateLimiter = fixture.getSut(); - fixture.dateTimeToReturn = 0; - - final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits( - '1:error:key, 5:error:organization', null, 1); - - expect(rateLimiter.filter(envelope), isNull); - }); - - test('transaction', () { - final rateLimiter = fixture.getSut(); - fixture.dateTimeToReturn = 0; - - final transaction = fixture.getTransaction(); - final eventItem = SentryEnvelopeItem.fromTransaction(transaction); - final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), - [eventItem], - ); - - rateLimiter.updateRetryAfterLimits( - '1:transaction:key, 5:transaction:organization', null, 1); - - final result = rateLimiter.filter(envelope); - expect(result, isNull); - }); - - test('log', () { - final rateLimiter = fixture.getSut(); - fixture.dateTimeToReturn = 0; - - final log = SentryLog( - timestamp: DateTime.now(), - traceId: SentryId.newId(), - level: SentryLogLevel.info, - body: 'test', - attributes: { - 'test': SentryLogAttribute.string('test'), - }, - ); - - final sdkVersion = SdkVersion(name: 'test', version: 'test'); - final envelope = SentryEnvelope.fromLogs([log], sdkVersion); - - rateLimiter.updateRetryAfterLimits( - '1:log_item:key, 5:log_item:organization', null, 1); - - final result = rateLimiter.filter(envelope); - expect(result, isNull); - }); - }); - - group('$DataCategory', () { - test('fromItemType', () { - expect(DataCategory.fromItemType('event'), DataCategory.error); - expect(DataCategory.fromItemType('session'), DataCategory.session); - expect(DataCategory.fromItemType('attachment'), DataCategory.attachment); - expect( - DataCategory.fromItemType('transaction'), DataCategory.transaction); - expect(DataCategory.fromItemType('statsd'), DataCategory.metricBucket); - expect(DataCategory.fromItemType('log'), DataCategory.logItem); - expect(DataCategory.fromItemType('unknown'), DataCategory.unknown); - }); - }); -} - -class Fixture { - var dateTimeToReturn = 0; - - late var mockRecorder = MockClientReportRecorder(); - - RateLimiter getSut() { - final options = defaultTestOptions(); - options.clock = _currentDateTime; - options.recorder = mockRecorder; - - return RateLimiter(options); - } - - DateTime _currentDateTime() { - return DateTime.fromMillisecondsSinceEpoch(dateTimeToReturn); - } - - SentryTransaction getTransaction() { - final context = SentryTransactionContext( - 'name', - 'op', - samplingDecision: SentryTracesSamplingDecision(true), - ); - final tracer = SentryTracer(context, MockHub()); - return SentryTransaction(tracer); - } -} diff --git a/dart/test/protocol/sdk_info_test.dart b/dart/test/protocol/sdk_info_test.dart deleted file mode 100644 index 612073eb82..0000000000 --- a/dart/test/protocol/sdk_info_test.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final sdkInfo = SdkInfo( - sdkName: 'sdkName', - versionMajor: 1, - versionMinor: 2, - versionPatchlevel: 3, - unknown: testUnknown, - ); - - final sdkInfoJson = { - 'sdk_name': 'sdkName', - 'version_major': 1, - 'version_minor': 2, - 'version_patchlevel': 3, - }; - sdkInfoJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = sdkInfo.toJson(); - - expect( - MapEquality().equals(sdkInfoJson, json), - true, - ); - }); - test('fromJson', () { - final sdkInfo = SdkInfo.fromJson(sdkInfoJson); - final json = sdkInfo.toJson(); - - print(sdkInfo); - print(json); - - expect( - MapEquality().equals(sdkInfoJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sdkInfo; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - test('copyWith takes new values', () { - final data = sdkInfo; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - sdkName: 'sdkName1', - versionMajor: 11, - versionMinor: 22, - versionPatchlevel: 33, - ); - - expect('sdkName1', copy.sdkName); - expect(11, copy.versionMajor); - expect(22, copy.versionMinor); - expect(33, copy.versionPatchlevel); - }); - }); -} diff --git a/dart/test/protocol/sdk_version_test.dart b/dart/test/protocol/sdk_version_test.dart deleted file mode 100644 index 26ddddbb40..0000000000 --- a/dart/test/protocol/sdk_version_test.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - group('json', () { - final fixture = Fixture(); - - test('toJson', () { - final json = fixture.getSut().toJson(); - - expect( - DeepCollectionEquality().equals(fixture.sdkVersionJson, json), - true, - ); - }); - test('fromJson', () { - final sdkVersion = SdkVersion.fromJson(fixture.sdkVersionJson); - final json = sdkVersion.toJson(); - - expect( - DeepCollectionEquality().equals(fixture.sdkVersionJson, json), - true, - ); - }); - }); - - group('copyWith', () { - final fixture = Fixture(); - - test('copyWith keeps unchanged', () { - final sut = fixture.getSut(); - // ignore: deprecated_member_use_from_same_package - final copy = sut.copyWith(); - - expect(sut.toJson(), copy.toJson()); - }); - - test('copyWith takes new values', () { - final sut = fixture.getSut(); - - final packages = [SentryPackage('name1', 'version1')]; - final integrations = ['test1']; - // ignore: deprecated_member_use_from_same_package - final copy = sut.copyWith( - name: 'name1', - version: 'version1', - integrations: integrations, - packages: packages, - ); - - expect( - ListEquality().equals(integrations, copy.integrations), - true, - ); - expect( - ListEquality().equals(packages, copy.packages), - true, - ); - expect('name1', copy.name); - expect('version1', copy.version); - }); - }); - - group('addPackage', () { - final fixture = Fixture(); - - test('add package if not same name and version', () { - final sut = fixture.getSut(); - sut.addPackage('name1', 'version1'); - - final last = sut.packages.last; - expect('name1', last.name); - expect('version1', last.version); - }); - test('does not add package if the same name and version', () { - final sut = fixture.getSut(); - sut.addPackage('name', 'version'); - - expect(1, sut.packages.length); - }); - }); -} - -class Fixture { - final sdkVersionJson = { - 'name': 'name', - 'version': 'version', - 'integrations': ['test'], - 'packages': [ - { - 'name': 'name', - 'version': 'version', - } - ], - }; - - Fixture() { - sdkVersionJson.addAll(testUnknown); - } - - SdkVersion getSut() => SdkVersion( - name: 'name', - version: 'version', - integrations: ['test'], - packages: [SentryPackage('name', 'version')], - unknown: testUnknown, - ); -} diff --git a/dart/test/protocol/sentry_app_test.dart b/dart/test/protocol/sentry_app_test.dart deleted file mode 100644 index 0bbf9c0fcf..0000000000 --- a/dart/test/protocol/sentry_app_test.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final testStartTime = DateTime.fromMicrosecondsSinceEpoch(0); - - final sentryApp = SentryApp( - name: 'fixture-name', - version: 'fixture-version', - identifier: 'fixture-identifier', - build: 'fixture-build', - buildType: 'fixture-buildType', - startTime: testStartTime, - deviceAppHash: 'fixture-deviceAppHash', - inForeground: true, - viewNames: ['fixture-viewName', 'fixture-viewName2'], - textScale: 2.0, - unknown: testUnknown, - ); - - final sentryAppJson = { - 'app_name': 'fixture-name', - 'app_version': 'fixture-version', - 'app_identifier': 'fixture-identifier', - 'app_build': 'fixture-build', - 'build_type': 'fixture-buildType', - 'app_start_time': testStartTime.toIso8601String(), - 'device_app_hash': 'fixture-deviceAppHash', - 'in_foreground': true, - 'view_names': ['fixture-viewName', 'fixture-viewName2'], - 'text_scale': 2.0, - }; - sentryAppJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = sentryApp.toJson(); - - expect(json['app_name'], 'fixture-name'); - expect(json['app_version'], 'fixture-version'); - expect(json['app_identifier'], 'fixture-identifier'); - expect(json['app_build'], 'fixture-build'); - expect(json['build_type'], 'fixture-buildType'); - expect(json['app_start_time'], testStartTime.toIso8601String()); - expect(json['device_app_hash'], 'fixture-deviceAppHash'); - expect(json['in_foreground'], true); - expect(json['view_names'], ['fixture-viewName', 'fixture-viewName2']); - expect(json['text_scale'], 2.0); - }); - test('fromJson', () { - final sentryApp = SentryApp.fromJson(sentryAppJson); - final json = sentryApp.toJson(); - - expect(json['app_name'], 'fixture-name'); - expect(json['app_version'], 'fixture-version'); - expect(json['app_identifier'], 'fixture-identifier'); - expect(json['app_build'], 'fixture-build'); - expect(json['build_type'], 'fixture-buildType'); - expect(json['app_start_time'], testStartTime.toIso8601String()); - expect(json['device_app_hash'], 'fixture-deviceAppHash'); - expect(json['in_foreground'], true); - expect(json['view_names'], ['fixture-viewName', 'fixture-viewName2']); - expect(json['text_scale'], 2.0); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryApp; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = sentryApp; - - final startTime = DateTime.now(); - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - name: 'name1', - version: 'version1', - identifier: 'identifier1', - build: 'build1', - buildType: 'buildType1', - startTime: startTime, - deviceAppHash: 'hash1', - inForeground: true, - viewNames: ['screen1'], - textScale: 3.0, - ); - - expect('name1', copy.name); - expect('version1', copy.version); - expect('identifier1', copy.identifier); - expect('build1', copy.build); - expect('buildType1', copy.buildType); - expect(startTime, copy.startTime); - expect('hash1', copy.deviceAppHash); - expect(true, copy.inForeground); - expect(['screen1'], copy.viewNames); - expect(3.0, copy.textScale); - }); - }); -} diff --git a/dart/test/protocol/sentry_baggage_header_test.dart b/dart/test/protocol/sentry_baggage_header_test.dart deleted file mode 100644 index ef792877f9..0000000000 --- a/dart/test/protocol/sentry_baggage_header_test.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - group('$SentryBaggageHeader', () { - test('name is baggage', () { - final baggage = SentryBaggageHeader(''); - - expect(baggage.name, 'baggage'); - }); - - test('baggage header from baggage', () { - final baggage = SentryBaggage({}); - final id = SentryId.newId().toString(); - baggage.setTraceId(id); - baggage.setPublicKey('publicKey'); - baggage.setRelease('release'); - baggage.setEnvironment('environment'); - baggage.setUserId('userId'); - baggage.setTransaction('transaction'); - baggage.setSampleRate('1.0'); - baggage.setSampleRand('0.4'); - baggage.setSampled('false'); - final replayId = SentryId.newId().toString(); - baggage.setReplayId(replayId); - - final baggageHeader = SentryBaggageHeader.fromBaggage(baggage); - - expect( - baggageHeader.value, - 'sentry-trace_id=$id,' - 'sentry-public_key=publicKey,' - 'sentry-release=release,' - 'sentry-environment=environment,' - 'sentry-user_id=userId,' - 'sentry-transaction=transaction,' - 'sentry-sample_rate=1.0,' - 'sentry-sample_rand=0.4,' - 'sentry-sampled=false,' - 'sentry-replay_id=$replayId'); - }); - }); -} diff --git a/dart/test/protocol/sentry_browser_test.dart b/dart/test/protocol/sentry_browser_test.dart deleted file mode 100644 index 13096237a9..0000000000 --- a/dart/test/protocol/sentry_browser_test.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - final sentryBrowser = SentryBrowser( - name: 'fixture-name', - version: 'fixture-version', - ); - - final sentryBrowserJson = { - 'name': 'fixture-name', - 'version': 'fixture-version', - }; - - group('json', () { - test('toJson', () { - final json = sentryBrowser.toJson(); - - expect( - MapEquality().equals(sentryBrowserJson, json), - true, - ); - }); - test('fromJson', () { - final sentryBrowser = SentryBrowser.fromJson(sentryBrowserJson); - final json = sentryBrowser.toJson(); - - expect( - MapEquality().equals(sentryBrowserJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryBrowser; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = sentryBrowser; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - name: 'name1', - version: 'version1', - ); - - expect('name1', copy.name); - expect('version1', copy.version); - }); - }); -} diff --git a/dart/test/protocol/sentry_device_test.dart b/dart/test/protocol/sentry_device_test.dart deleted file mode 100644 index 1921f23fee..0000000000 --- a/dart/test/protocol/sentry_device_test.dart +++ /dev/null @@ -1,350 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final testBootTime = DateTime.fromMicrosecondsSinceEpoch(0); - - final sentryDevice = SentryDevice( - name: 'testDevice', - family: 'testFamily', - model: 'testModel', - modelId: 'testModelId', - arch: 'testArch', - batteryLevel: 23.0, - orientation: SentryOrientation.landscape, - manufacturer: 'testOEM', - brand: 'testBrand', - screenDensity: 99.1, - screenDpi: 100, - online: false, - charging: true, - lowMemory: false, - simulator: true, - memorySize: 1234567, - freeMemory: 12345, - usableMemory: 9876, - storageSize: 1234567, - freeStorage: 1234567, - externalStorageSize: 98765, - externalFreeStorage: 98765, - bootTime: testBootTime, - batteryStatus: 'Unknown', - cpuDescription: 'M1 Pro Max Ultra', - deviceType: 'Flutter Device', - deviceUniqueIdentifier: 'uuid', - processorCount: 4, - processorFrequency: 1.2, - supportsAccelerometer: true, - supportsGyroscope: true, - supportsAudio: true, - supportsLocationService: true, - supportsVibration: true, - screenHeightPixels: 100, - screenWidthPixels: 100, - unknown: testUnknown, - ); - - final sentryDeviceJson = { - 'name': 'testDevice', - 'family': 'testFamily', - 'model': 'testModel', - 'model_id': 'testModelId', - 'arch': 'testArch', - 'battery_level': 23.0, - 'orientation': 'landscape', - 'manufacturer': 'testOEM', - 'brand': 'testBrand', - 'screen_density': 99.1, - 'screen_dpi': 100, - 'online': false, - 'charging': true, - 'low_memory': false, - 'simulator': true, - 'memory_size': 1234567, - 'free_memory': 12345, - 'usable_memory': 9876, - 'storage_size': 1234567, - 'free_storage': 1234567, - 'external_storage_size': 98765, - 'external_free_storage': 98765, - 'boot_time': testBootTime.toIso8601String(), - 'battery_status': 'Unknown', - 'cpu_description': 'M1 Pro Max Ultra', - 'device_type': 'Flutter Device', - 'device_unique_identifier': 'uuid', - 'processor_count': 4, - 'processor_frequency': 1.2, - 'supports_accelerometer': true, - 'supports_gyroscope': true, - 'supports_audio': true, - 'supports_location_service': true, - 'supports_vibration': true, - 'screen_height_pixels': 100, - 'screen_width_pixels': 100, - }; - sentryDeviceJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = sentryDevice.toJson(); - - expect( - MapEquality().equals(sentryDeviceJson, json), - true, - ); - }); - - test('fromJson', () { - final sentryDevice = SentryDevice.fromJson(sentryDeviceJson); - final json = sentryDevice.toJson(); - - expect( - MapEquality().equals(sentryDeviceJson, json), - true, - ); - }); - - test('fromJson double screen_height_pixels and screen_width_pixels', () { - sentryDeviceJson['screen_height_pixels'] = 100.0; - sentryDeviceJson['screen_width_pixels'] = 100.0; - - final sentryDevice = SentryDevice.fromJson(sentryDeviceJson); - final json = sentryDevice.toJson(); - - expect( - MapEquality().equals(sentryDeviceJson, json), - true, - ); - }); - - test('batery level converts int to double', () { - final map = {'battery_level': 1}; - - final sentryDevice = SentryDevice.fromJson(map); - - expect( - sentryDevice.batteryLevel, - 1.0, - ); - }); - - test('batery level maps double', () { - final map = {'battery_level': 1.0}; - - final sentryDevice = SentryDevice.fromJson(map); - - expect( - sentryDevice.batteryLevel, - 1.0, - ); - }); - - test('batery level ignores if not a num', () { - final map = {'battery_level': 'abc'}; - - final sentryDevice = SentryDevice.fromJson(map); - - expect( - sentryDevice.batteryLevel, - null, - ); - }); - }); - - test('copyWith keeps unchanged', () { - final data = _generate(); - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = _generate(); - - final bootTime = DateTime.now(); - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - name: 'name1', - family: 'family1', - model: 'model1', - modelId: 'modelId1', - arch: 'arch1', - batteryLevel: 2, - orientation: SentryOrientation.portrait, - manufacturer: 'manufacturer1', - brand: 'brand1', - screenHeightPixels: 900, - screenWidthPixels: 700, - screenDensity: 99.2, - screenDpi: 99, - online: true, - charging: false, - lowMemory: true, - simulator: false, - memorySize: 12345678, - freeMemory: 123456, - usableMemory: 98765, - storageSize: 12345678, - freeStorage: 12345678, - externalStorageSize: 987654, - externalFreeStorage: 987654, - bootTime: bootTime, - ); - - expect('name1', copy.name); - expect('family1', copy.family); - expect('model1', copy.model); - expect('modelId1', copy.modelId); - expect('arch1', copy.arch); - expect(2, copy.batteryLevel); - expect(SentryOrientation.portrait, copy.orientation); - expect('manufacturer1', copy.manufacturer); - expect('brand1', copy.brand); - expect(900, copy.screenHeightPixels); - expect(700, copy.screenWidthPixels); - expect(99.2, copy.screenDensity); - expect(99, copy.screenDpi); - expect(true, copy.online); - expect(false, copy.charging); - expect(true, copy.lowMemory); - expect(false, copy.simulator); - expect(12345678, copy.memorySize); - expect(123456, copy.freeMemory); - expect(98765, copy.usableMemory); - expect(12345678, copy.storageSize); - expect(12345678, copy.freeStorage); - expect(987654, copy.externalStorageSize); - expect(987654, copy.externalFreeStorage); - expect(bootTime, copy.bootTime); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryDevice; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = sentryDevice; - - final bootTime = DateTime.now(); - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - name: 'name1', - family: 'family1', - model: 'model1', - modelId: 'modelId1', - arch: 'arch1', - batteryLevel: 2, - orientation: SentryOrientation.portrait, - manufacturer: 'manufacturer1', - brand: 'brand1', - screenDensity: 99.2, - screenDpi: 99, - online: true, - charging: false, - lowMemory: true, - simulator: false, - memorySize: 12345678, - freeMemory: 123456, - usableMemory: 98765, - storageSize: 12345678, - freeStorage: 12345678, - externalStorageSize: 987654, - externalFreeStorage: 987654, - bootTime: bootTime, - batteryStatus: 'Charging', - cpuDescription: 'Intel i9', - deviceType: 'Tablet', - deviceUniqueIdentifier: 'foo_bar_baz', - processorCount: 8, - processorFrequency: 3.4, - supportsAccelerometer: false, - supportsGyroscope: false, - supportsAudio: false, - supportsLocationService: false, - supportsVibration: false, - screenHeightPixels: 2, - screenWidthPixels: 2, - ); - - expect('name1', copy.name); - expect('family1', copy.family); - expect('model1', copy.model); - expect('modelId1', copy.modelId); - expect('arch1', copy.arch); - expect(2, copy.batteryLevel); - expect(SentryOrientation.portrait, copy.orientation); - expect('manufacturer1', copy.manufacturer); - expect('brand1', copy.brand); - expect(99.2, copy.screenDensity); - expect(99, copy.screenDpi); - expect(true, copy.online); - expect(false, copy.charging); - expect(true, copy.lowMemory); - expect(false, copy.simulator); - expect(12345678, copy.memorySize); - expect(123456, copy.freeMemory); - expect(98765, copy.usableMemory); - expect(12345678, copy.storageSize); - expect(12345678, copy.freeStorage); - expect(987654, copy.externalStorageSize); - expect(987654, copy.externalFreeStorage); - expect(bootTime, copy.bootTime); - expect('Charging', copy.batteryStatus); - expect('Intel i9', copy.cpuDescription); - expect('Tablet', copy.deviceType); - expect('foo_bar_baz', copy.deviceUniqueIdentifier); - expect(8, copy.processorCount); - expect(3.4, copy.processorFrequency); - expect(false, copy.supportsAccelerometer); - expect(false, copy.supportsGyroscope); - expect(false, copy.supportsAudio); - expect(false, copy.supportsLocationService); - expect(false, copy.supportsVibration); - expect(2, copy.screenHeightPixels); - expect(2, copy.screenWidthPixels); - }); - }); -} - -SentryDevice _generate({DateTime? testBootTime}) => SentryDevice( - name: 'name', - family: 'family', - model: 'model', - modelId: 'modelId', - arch: 'arch', - batteryLevel: 1, - orientation: SentryOrientation.landscape, - manufacturer: 'manufacturer', - brand: 'brand', - screenHeightPixels: 600, - screenWidthPixels: 800, - screenDensity: 99.1, - screenDpi: 100, - online: false, - charging: true, - lowMemory: false, - simulator: true, - memorySize: 1234567, - freeMemory: 12345, - usableMemory: 9876, - storageSize: 1234567, - freeStorage: 1234567, - externalStorageSize: 98765, - externalFreeStorage: 98765, - bootTime: testBootTime ?? DateTime.now(), - ); diff --git a/dart/test/protocol/sentry_exception_test.dart b/dart/test/protocol/sentry_exception_test.dart deleted file mode 100644 index 7b2e4e0b08..0000000000 --- a/dart/test/protocol/sentry_exception_test.dart +++ /dev/null @@ -1,158 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final sentryException = SentryException( - type: 'type', - value: 'value', - module: 'module', - stackTrace: SentryStackTrace(frames: [SentryStackFrame(absPath: 'abs')]), - mechanism: Mechanism(type: 'type'), - threadId: 1, - unknown: testUnknown, - ); - - final sentryExceptionJson = { - 'type': 'type', - 'value': 'value', - 'module': 'module', - 'stacktrace': { - 'frames': [ - {'abs_path': 'abs'} - ] - }, - 'mechanism': {'type': 'type'}, - 'thread_id': 1, - }; - sentryExceptionJson.addAll(testUnknown); - - group('json', () { - test('fromJson', () { - final sentryException = SentryException.fromJson(sentryExceptionJson); - final json = sentryException.toJson(); - - expect( - DeepCollectionEquality().equals(sentryExceptionJson, json), - true, - ); - }); - - test('should serialize stacktrace', () { - final mechanism = Mechanism( - type: 'mechanism-example', - description: 'a mechanism', - handled: true, - synthetic: false, - helpLink: 'https://help.com', - data: {'polyfill': 'bluebird'}, - meta: { - 'signal': { - 'number': 10, - 'code': 0, - 'name': 'SIGBUS', - 'code_name': 'BUS_NOOP' - } - }, - ); - final stacktrace = SentryStackTrace(frames: [ - SentryStackFrame( - absPath: 'frame-path', - fileName: 'example.dart', - function: 'parse', - module: 'example-module', - lineNo: 1, - colNo: 2, - contextLine: 'context-line example', - inApp: true, - package: 'example-package', - native: false, - platform: 'dart', - rawFunction: 'example-rawFunction', - framesOmitted: [1, 2, 3], - ), - ]); - - final sentryException = SentryException( - type: 'StateError', - value: 'Bad state: error', - module: 'example.module', - stackTrace: stacktrace, - mechanism: mechanism, - threadId: 123456, - ); - - final serialized = sentryException.toJson(); - - expect(serialized['type'], 'StateError'); - expect(serialized['value'], 'Bad state: error'); - expect(serialized['module'], 'example.module'); - expect(serialized['thread_id'], 123456); - expect(serialized['mechanism']['type'], 'mechanism-example'); - expect(serialized['mechanism']['description'], 'a mechanism'); - expect(serialized['mechanism']['handled'], true); - expect(serialized['mechanism']['synthetic'], false); - expect(serialized['mechanism']['help_link'], 'https://help.com'); - expect(serialized['mechanism']['data'], {'polyfill': 'bluebird'}); - expect(serialized['mechanism']['meta'], { - 'signal': { - 'number': 10, - 'code': 0, - 'name': 'SIGBUS', - 'code_name': 'BUS_NOOP' - } - }); - - final serializedFrame = serialized['stacktrace']['frames'].first; - expect(serializedFrame['abs_path'], 'frame-path'); - expect(serializedFrame['filename'], 'example.dart'); - expect(serializedFrame['function'], 'parse'); - expect(serializedFrame['module'], 'example-module'); - expect(serializedFrame['lineno'], 1); - expect(serializedFrame['colno'], 2); - expect(serializedFrame['context_line'], 'context-line example'); - expect(serializedFrame['in_app'], true); - expect(serializedFrame['package'], 'example-package'); - expect(serializedFrame['native'], false); - expect(serializedFrame['platform'], 'dart'); - expect(serializedFrame['raw_function'], 'example-rawFunction'); - expect(serializedFrame['frames_omitted'], [1, 2, 3]); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryException; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect(data.toJson(), copy.toJson()); - }); - - test('copyWith takes new values', () { - final data = sentryException; - - final stackTrace = - SentryStackTrace(frames: [SentryStackFrame(absPath: 'abs1')]); - final mechanism = Mechanism(type: 'type1'); - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - type: 'type1', - value: 'value1', - module: 'module1', - stackTrace: stackTrace, - mechanism: mechanism, - threadId: 2, - ); - - expect('type1', copy.type); - expect('value1', copy.value); - expect('module1', copy.module); - expect(2, copy.threadId); - expect(mechanism.toJson(), copy.mechanism!.toJson()); - expect(stackTrace.toJson(), copy.stackTrace!.toJson()); - }); - }); -} diff --git a/dart/test/protocol/sentry_feature_flag_tests.dart b/dart/test/protocol/sentry_feature_flag_tests.dart deleted file mode 100644 index 272c6763ce..0000000000 --- a/dart/test/protocol/sentry_feature_flag_tests.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final featureFlag = SentryFeatureFlag( - flag: 'feature_flag_1', - result: true, - unknown: testUnknown, - ); - final featureFlagJson = { - ...testUnknown, - 'flag': 'feature_flag_1', - 'result': true, - }; - - group('json', () { - test('toJson', () { - final json = featureFlag.toJson(); - expect( - DeepCollectionEquality().equals(featureFlagJson, json), - true, - ); - }); - - test('fromJson', () { - final featureFlag = SentryFeatureFlag.fromJson(featureFlagJson); - final json = featureFlag.toJson(); - - expect( - DeepCollectionEquality().equals(featureFlagJson, json), - true, - ); - }); - }); -} diff --git a/dart/test/protocol/sentry_feature_flags_tests.dart b/dart/test/protocol/sentry_feature_flags_tests.dart deleted file mode 100644 index 63c6712508..0000000000 --- a/dart/test/protocol/sentry_feature_flags_tests.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final featureFlags = SentryFeatureFlags( - values: [ - SentryFeatureFlag(flag: 'feature_flag_1', result: true), - SentryFeatureFlag(flag: 'feature_flag_2', result: false), - ], - unknown: testUnknown, - ); - final featureFlagsJson = { - ...testUnknown, - 'values': [ - {'name': 'feature_flag_1', 'value': true}, - {'name': 'feature_flag_2', 'value': false}, - ], - }; - - group('json', () { - test('toJson', () { - final json = featureFlags.toJson(); - expect( - DeepCollectionEquality().equals(featureFlagsJson, json), - true, - ); - }); - - test('fromJson', () { - final featureFlags = SentryFeatureFlags.fromJson(featureFlagsJson); - final json = featureFlags.toJson(); - - expect( - DeepCollectionEquality().equals(featureFlagsJson, json), - true, - ); - }); - }); -} diff --git a/dart/test/protocol/sentry_feedback_test.dart b/dart/test/protocol/sentry_feedback_test.dart deleted file mode 100644 index 28640aa09e..0000000000 --- a/dart/test/protocol/sentry_feedback_test.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final associatedEventId = SentryId.fromId('8a32c0f9be1d34a5efb2c4a10d80de9a'); - - final feedback = SentryFeedback( - message: 'fixture-message', - contactEmail: 'fixture-contactEmail', - name: 'fixture-name', - replayId: 'fixture-replayId', - url: "https://fixture-url.com", - associatedEventId: associatedEventId, - unknown: testUnknown, - ); - - final feedbackJson = { - 'message': 'fixture-message', - 'contact_email': 'fixture-contactEmail', - 'name': 'fixture-name', - 'replay_id': 'fixture-replayId', - 'url': 'https://fixture-url.com', - 'associated_event_id': '8a32c0f9be1d34a5efb2c4a10d80de9a', - }; - feedbackJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = feedback.toJson(); - - expect( - MapEquality().equals(feedbackJson, json), - true, - ); - }); - test('fromJson', () { - final feedback = SentryFeedback.fromJson(feedbackJson); - final json = feedback.toJson(); - - print(feedback); - print(json); - - expect( - MapEquality().equals(feedbackJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = feedback; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - test('copyWith takes new values', () { - final data = feedback; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - message: 'fixture-2-message', - contactEmail: 'fixture-2-contactEmail', - name: 'fixture-2-name', - replayId: 'fixture-2-replayId', - url: "https://fixture-2-url.com", - associatedEventId: SentryId.fromId('1d49af08b6e2c437f9052b1ecfd83dca'), - ); - - expect(copy.message, 'fixture-2-message'); - expect(copy.contactEmail, 'fixture-2-contactEmail'); - expect(copy.name, 'fixture-2-name'); - expect(copy.replayId, 'fixture-2-replayId'); - expect(copy.url, "https://fixture-2-url.com"); - expect(copy.associatedEventId.toString(), - '1d49af08b6e2c437f9052b1ecfd83dca'); - }); - }); -} diff --git a/dart/test/protocol/sentry_gpu_test.dart b/dart/test/protocol/sentry_gpu_test.dart deleted file mode 100644 index 44c4b662c7..0000000000 --- a/dart/test/protocol/sentry_gpu_test.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final sentryGpu = SentryGpu( - name: 'fixture-name', - id: 1, - vendorId: '2', - vendorName: 'fixture-vendorName', - memorySize: 3, - apiType: 'fixture-apiType', - multiThreadedRendering: true, - version: '4', - npotSupport: 'fixture-npotSupport', - unknown: testUnknown, - ); - - final sentryGpuJson = { - 'name': 'fixture-name', - 'id': 1, - 'vendor_id': '2', - 'vendor_name': 'fixture-vendorName', - 'memory_size': 3, - 'api_type': 'fixture-apiType', - 'multi_threaded_rendering': true, - 'version': '4', - 'npot_support': 'fixture-npotSupport' - }; - sentryGpuJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = sentryGpu.toJson(); - - expect( - MapEquality().equals(sentryGpuJson, json), - true, - ); - }); - test('fromJson', () { - final sentryGpu = SentryGpu.fromJson(sentryGpuJson); - final json = sentryGpu.toJson(); - - expect( - MapEquality().equals(sentryGpuJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryGpu; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - test('copyWith takes new values', () { - final data = sentryGpu; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - name: 'name1', - id: 11, - vendorId: '22', - vendorName: 'vendorName1', - memorySize: 33, - apiType: 'apiType1', - multiThreadedRendering: false, - version: 'version1', - npotSupport: 'npotSupport1', - ); - - expect('name1', copy.name); - expect(11, copy.id); - expect('22', copy.vendorId); - expect('vendorName1', copy.vendorName); - expect(33, copy.memorySize); - expect('apiType1', copy.apiType); - expect(false, copy.multiThreadedRendering); - expect('version1', copy.version); - expect('npotSupport1', copy.npotSupport); - }); - }); -} diff --git a/dart/test/protocol/sentry_log_attribute_test.dart b/dart/test/protocol/sentry_log_attribute_test.dart deleted file mode 100644 index 2c0fb7ce31..0000000000 --- a/dart/test/protocol/sentry_log_attribute_test.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:test/test.dart'; -import 'package:sentry/sentry.dart'; - -void main() { - test('$SentryLogAttribute string to json', () { - final attribute = SentryLogAttribute.string('test'); - final json = attribute.toJson(); - expect(json, { - 'value': 'test', - 'type': 'string', - }); - }); - - test('$SentryLogAttribute bool to json', () { - final attribute = SentryLogAttribute.bool(true); - final json = attribute.toJson(); - expect(json, { - 'value': true, - 'type': 'boolean', - }); - }); - - test('$SentryLogAttribute int to json', () { - final attribute = SentryLogAttribute.int(1); - final json = attribute.toJson(); - - expect(json, { - 'value': 1, - 'type': 'integer', - }); - }); - - test('$SentryLogAttribute double to json', () { - final attribute = SentryLogAttribute.double(1.0); - final json = attribute.toJson(); - - expect(json, { - 'value': 1.0, - 'type': 'double', - }); - }); -} diff --git a/dart/test/protocol/sentry_log_test.dart b/dart/test/protocol/sentry_log_test.dart deleted file mode 100644 index 0921df7a32..0000000000 --- a/dart/test/protocol/sentry_log_test.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:test/test.dart'; -import 'package:sentry/sentry.dart'; - -void main() { - test('$SentryLog to json', () { - final timestamp = DateTime.now(); - final traceId = SentryId.newId(); - - final logItem = SentryLog( - timestamp: timestamp, - traceId: traceId, - level: SentryLogLevel.info, - body: 'fixture-body', - attributes: { - 'test': SentryLogAttribute.string('fixture-test'), - 'test2': SentryLogAttribute.bool(true), - 'test3': SentryLogAttribute.int(9001), - 'test4': SentryLogAttribute.double(9000.1), - }, - severityNumber: 1, - ); - - final json = logItem.toJson(); - - expect(json, { - 'timestamp': timestamp.toIso8601String(), - 'trace_id': traceId.toString(), - 'level': 'info', - 'body': 'fixture-body', - 'attributes': { - 'test': { - 'value': 'fixture-test', - 'type': 'string', - }, - 'test2': { - 'value': true, - 'type': 'boolean', - }, - 'test3': { - 'value': 9001, - 'type': 'integer', - }, - 'test4': { - 'value': 9000.1, - 'type': 'double', - }, - }, - 'severity_number': 1, - }); - }); - - test('$SentryLevel without severity number infers from level in toJson', () { - final logItem = SentryLog( - timestamp: DateTime.now(), - traceId: SentryId.newId(), - level: SentryLogLevel.trace, - body: 'fixture-body', - attributes: { - 'test': SentryLogAttribute.string('fixture-test'), - }, - ); - - var json = logItem.toJson(); - expect(json['severity_number'], 1); - - logItem.level = SentryLogLevel.debug; - json = logItem.toJson(); - expect(json['severity_number'], 5); - - logItem.level = SentryLogLevel.info; - json = logItem.toJson(); - expect(json['severity_number'], 9); - - logItem.level = SentryLogLevel.warn; - json = logItem.toJson(); - expect(json['severity_number'], 13); - - logItem.level = SentryLogLevel.error; - json = logItem.toJson(); - expect(json['severity_number'], 17); - - logItem.level = SentryLogLevel.fatal; - json = logItem.toJson(); - expect(json['severity_number'], 21); - }); -} diff --git a/dart/test/protocol/sentry_message_test.dart b/dart/test/protocol/sentry_message_test.dart deleted file mode 100644 index d7d9260a8e..0000000000 --- a/dart/test/protocol/sentry_message_test.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final sentryMessage = SentryMessage( - 'message 1', - template: 'message %d', - params: ['1'], - unknown: testUnknown, - ); - - final sentryMessageJson = { - 'formatted': 'message 1', - 'message': 'message %d', - 'params': ['1'], - }; - sentryMessageJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = sentryMessage.toJson(); - - expect( - DeepCollectionEquality().equals(sentryMessageJson, json), - true, - ); - }); - test('fromJson', () { - final sentryMessage = SentryMessage.fromJson(sentryMessageJson); - final json = sentryMessage.toJson(); - - expect( - DeepCollectionEquality().equals(sentryMessageJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryMessage; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = sentryMessage; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - formatted: 'message 21', - template: 'message 2 %d', - params: ['2'], - ); - - expect('message 21', copy.formatted); - expect('message 2 %d', copy.template); - expect(['2'], copy.params); - }); - }); -} diff --git a/dart/test/protocol/sentry_operating_system_test.dart b/dart/test/protocol/sentry_operating_system_test.dart deleted file mode 100644 index 5054642f81..0000000000 --- a/dart/test/protocol/sentry_operating_system_test.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final sentryOperatingSystem = SentryOperatingSystem( - name: 'fixture-name', - version: 'fixture-version', - build: 'fixture-build', - kernelVersion: 'fixture-kernelVersion', - rooted: true, - rawDescription: 'fixture-rawDescription', - unknown: testUnknown, - ); - - final sentryOperatingSystemJson = { - 'name': 'fixture-name', - 'version': 'fixture-version', - 'build': 'fixture-build', - 'kernel_version': 'fixture-kernelVersion', - 'rooted': true, - 'raw_description': 'fixture-rawDescription' - }; - sentryOperatingSystemJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = sentryOperatingSystem.toJson(); - - expect( - MapEquality().equals(sentryOperatingSystemJson, json), - true, - ); - }); - test('fromJson', () { - final sentryOperatingSystem = - SentryOperatingSystem.fromJson(sentryOperatingSystemJson); - final json = sentryOperatingSystem.toJson(); - - expect( - MapEquality().equals(sentryOperatingSystemJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryOperatingSystem; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = sentryOperatingSystem; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - name: 'name1', - version: 'version1', - build: 'build1', - kernelVersion: 'kernelVersion1', - rooted: true, - ); - - expect('name1', copy.name); - expect('version1', copy.version); - expect('build1', copy.build); - expect('kernelVersion1', copy.kernelVersion); - expect(true, copy.rooted); - }); - }); -} diff --git a/dart/test/protocol/sentry_package_test.dart b/dart/test/protocol/sentry_package_test.dart deleted file mode 100644 index 8ec75db548..0000000000 --- a/dart/test/protocol/sentry_package_test.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final sentryPackage = SentryPackage( - 'name', - 'version', - unknown: testUnknown, - ); - - final sentryPackageJson = { - 'name': 'name', - 'version': 'version', - }; - sentryPackageJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = sentryPackage.toJson(); - - expect( - MapEquality().equals(sentryPackageJson, json), - true, - ); - }); - test('fromJson', () { - final sentryPackage = SdkVersion.fromJson(sentryPackageJson); - final json = sentryPackage.toJson(); - - expect( - MapEquality().equals(sentryPackageJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryPackage; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - test('copyWith takes new values', () { - final data = sentryPackage; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - name: 'name1', - version: 'version1', - ); - - expect('name1', copy.name); - expect('version1', copy.version); - }); - }); -} diff --git a/dart/test/protocol/sentry_proxy_test.dart b/dart/test/protocol/sentry_proxy_test.dart deleted file mode 100644 index 4aefef09b3..0000000000 --- a/dart/test/protocol/sentry_proxy_test.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - final proxy = SentryProxy( - host: 'localhost', - port: 8080, - type: SentryProxyType.http, - user: 'admin', - pass: '0000', - ); - - final proxyJson = { - 'host': 'localhost', - 'port': 8080, - 'type': 'HTTP', - 'user': 'admin', - 'pass': '0000', - }; - - group('toPacString', () { - test('returns "DIRECT" for ProxyType.direct', () { - SentryProxy proxy = SentryProxy(type: SentryProxyType.direct); - expect(proxy.toPacString(), equals('DIRECT')); - }); - - test('returns "PROXY host:port" for ProxyType.http with host and port', () { - SentryProxy proxy = SentryProxy( - type: SentryProxyType.http, host: 'localhost', port: 8080); - expect(proxy.toPacString(), equals('PROXY localhost:8080')); - }); - - test('returns "PROXY host" for ProxyType.http with host only', () { - SentryProxy proxy = - SentryProxy(type: SentryProxyType.http, host: 'localhost'); - expect(proxy.toPacString(), equals('PROXY localhost')); - }); - - test('returns "SOCKS host:port" for ProxyType.socks with host and port', - () { - SentryProxy proxy = SentryProxy( - type: SentryProxyType.socks, host: 'localhost', port: 8080); - expect(proxy.toPacString(), equals('SOCKS localhost:8080')); - }); - - test('returns "SOCKS host" for ProxyType.socks with host only', () { - SentryProxy proxy = - SentryProxy(type: SentryProxyType.socks, host: 'localhost'); - expect(proxy.toPacString(), equals('SOCKS localhost')); - }); - - test('falls back to "DIRECT" if http is missing host', () { - SentryProxy proxy = SentryProxy(type: SentryProxyType.http); - expect(proxy.toPacString(), equals('DIRECT')); - }); - - test('falls back to "DIRECT" if socks is missing host', () { - SentryProxy proxy = SentryProxy(type: SentryProxyType.socks); - expect(proxy.toPacString(), equals('DIRECT')); - }); - }); - - group('json', () { - test('toJson', () { - final json = proxy.toJson(); - - expect( - DeepCollectionEquality().equals(proxyJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = proxy; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect(data.toJson(), copy.toJson()); - }); - - test('copyWith takes new values', () { - final data = proxy; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - host: 'localhost-2', - port: 9001, - type: SentryProxyType.socks, - user: 'user', - pass: '1234', - ); - - expect('localhost-2', copy.host); - expect(9001, copy.port); - expect(SentryProxyType.socks, copy.type); - expect('user', copy.user); - expect('1234', copy.pass); - }); - }); -} diff --git a/dart/test/protocol/sentry_request_test.dart b/dart/test/protocol/sentry_request_test.dart deleted file mode 100644 index ee1d65d290..0000000000 --- a/dart/test/protocol/sentry_request_test.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final sentryRequest = SentryRequest( - url: 'url', - method: 'method', - queryString: 'queryString', - cookies: 'cookies', - data: {'key': 'value'}, - headers: {'header_key': 'header_value'}, - env: {'env_key': 'env_value'}, - apiTarget: 'GraphQL', - unknown: testUnknown, - ); - - final sentryRequestJson = { - 'url': 'url', - 'method': 'method', - 'query_string': 'queryString', - 'cookies': 'cookies', - 'data': {'key': 'value'}, - 'headers': {'header_key': 'header_value'}, - 'env': {'env_key': 'env_value'}, - 'api_target': 'GraphQL', - }; - sentryRequestJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = sentryRequest.toJson(); - - expect( - DeepCollectionEquality().equals(sentryRequestJson, json), - true, - ); - }); - test('fromJson', () { - final sentryRequest = SentryRequest.fromJson(sentryRequestJson); - final json = sentryRequest.toJson(); - - expect( - DeepCollectionEquality().equals(sentryRequestJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryRequest; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - DeepCollectionEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = sentryRequest; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - url: 'url1', - method: 'method1', - queryString: 'queryString1', - cookies: 'cookies1', - data: {'key1': 'value1'}, - ); - - expect('url1', copy.url); - expect('method1', copy.method); - expect('queryString1', copy.queryString); - expect('cookies1', copy.cookies); - expect({'key1': 'value1'}, copy.data); - }); - }); - - test('SentryRequest.fromUri', () { - final request = SentryRequest.fromUri( - uri: Uri.parse('https://example.org/foo/bar?key=value#fragment'), - ); - - expect(request.url, 'https://example.org/foo/bar'); - expect(request.fragment, 'fragment'); - expect(request.queryString, 'key=value'); - }); -} diff --git a/dart/test/protocol/sentry_response_test.dart b/dart/test/protocol/sentry_response_test.dart deleted file mode 100644 index 216cca190f..0000000000 --- a/dart/test/protocol/sentry_response_test.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - final sentryResponse = SentryResponse( - bodySize: 42, - statusCode: 200, - headers: {'header_key': 'header_value'}, - cookies: 'foo=bar, another=cookie', - data: 'foo', - ); - - final sentryResponseJson = { - 'body_size': 42, - 'status_code': 200, - 'headers': {'header_key': 'header_value'}, - 'cookies': 'foo=bar, another=cookie', - 'data': 'foo', - }; - - group('json', () { - test('toJson', () { - final json = sentryResponse.toJson(); - - expect( - DeepCollectionEquality().equals(sentryResponseJson, json), - true, - ); - }); - test('fromJson', () { - final sentryResponse = SentryResponse.fromJson(sentryResponseJson); - final json = sentryResponse.toJson(); - - expect( - DeepCollectionEquality().equals(sentryResponseJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryResponse; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - expect(data.toJson(), copy.toJson()); - }); - - test('copyWith takes new values', () { - final data = sentryResponse; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - bodySize: 11, - headers: {'key1': 'value1'}, - statusCode: 301, - ); - - expect(11, copy.bodySize); - expect({'key1': 'value1'}, copy.headers); - expect(301, copy.statusCode); - }); - }); -} diff --git a/dart/test/protocol/sentry_runtime_test.dart b/dart/test/protocol/sentry_runtime_test.dart deleted file mode 100644 index 852738de42..0000000000 --- a/dart/test/protocol/sentry_runtime_test.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final sentryRuntime = SentryRuntime( - key: 'key', - name: 'name', - version: 'version', - rawDescription: 'rawDescription', - unknown: testUnknown, - ); - - final sentryRuntimeJson = { - 'name': 'name', - 'version': 'version', - 'raw_description': 'rawDescription', - }; - sentryRuntimeJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = sentryRuntime.toJson(); - - expect( - MapEquality().equals(sentryRuntimeJson, json), - true, - ); - }); - test('fromJson', () { - final sentryRuntime = SentryRuntime.fromJson(sentryRuntimeJson); - final json = sentryRuntime.toJson(); - - expect( - MapEquality().equals(sentryRuntimeJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryRuntime; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = sentryRuntime; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - key: 'key1', - name: 'name1', - version: 'version1', - rawDescription: 'rawDescription1', - ); - - expect('key1', copy.key); - expect('name1', copy.name); - expect('version1', copy.version); - expect('rawDescription1', copy.rawDescription); - }); - }); -} diff --git a/dart/test/protocol/sentry_stack_frame_test.dart b/dart/test/protocol/sentry_stack_frame_test.dart deleted file mode 100644 index 7955b31053..0000000000 --- a/dart/test/protocol/sentry_stack_frame_test.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final sentryStackFrame = SentryStackFrame( - absPath: 'absPath', - fileName: 'fileName', - function: 'function', - module: 'module', - lineNo: 1, - colNo: 2, - contextLine: 'contextLine', - inApp: true, - package: 'package', - native: false, - platform: 'platform', - imageAddr: 'imageAddr', - symbolAddr: 'symbolAddr', - instructionAddr: 'instructionAddr', - rawFunction: 'rawFunction', - framesOmitted: [1], - preContext: ['a'], - postContext: ['b'], - vars: {'key': 'value'}, - unknown: testUnknown, - ); - - final sentryStackFrameJson = { - 'pre_context': ['a'], - 'post_context': ['b'], - 'vars': {'key': 'value'}, - 'frames_omitted': [1], - 'filename': 'fileName', - 'package': 'package', - 'function': 'function', - 'module': 'module', - 'lineno': 1, - 'colno': 2, - 'abs_path': 'absPath', - 'context_line': 'contextLine', - 'in_app': true, - 'native': false, - 'platform': 'platform', - 'image_addr': 'imageAddr', - 'symbol_addr': 'symbolAddr', - 'instruction_addr': 'instructionAddr', - 'raw_function': 'rawFunction', - }; - sentryStackFrameJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = sentryStackFrame.toJson(); - - expect( - DeepCollectionEquality().equals(sentryStackFrameJson, json), - true, - ); - }); - test('fromJson', () { - final sentryStackFrame = SentryStackFrame.fromJson(sentryStackFrameJson); - final json = sentryStackFrame.toJson(); - - expect( - DeepCollectionEquality().equals(sentryStackFrameJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryStackFrame; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect(data.toJson(), copy.toJson()); - }); - test('copyWith takes new values', () { - final data = sentryStackFrame; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - absPath: 'absPath1', - fileName: 'fileName1', - function: 'function1', - module: 'module1', - lineNo: 11, - colNo: 22, - contextLine: 'contextLine1', - inApp: false, - package: 'package1', - native: true, - platform: 'platform1', - imageAddr: 'imageAddr1', - symbolAddr: 'symbolAddr1', - instructionAddr: 'instructionAddr1', - rawFunction: 'rawFunction1', - framesOmitted: [11], - preContext: ['ab'], - postContext: ['bb'], - vars: {'key1': 'value1'}, - ); - - expect('absPath1', copy.absPath); - expect('fileName1', copy.fileName); - expect('function1', copy.function); - expect('module1', copy.module); - expect(11, copy.lineNo); - expect(22, copy.colNo); - expect(false, copy.inApp); - expect('package1', copy.package); - expect(true, copy.native); - expect('platform1', copy.platform); - expect('imageAddr1', copy.imageAddr); - expect('symbolAddr1', copy.symbolAddr); - expect('instructionAddr1', copy.instructionAddr); - expect('rawFunction1', copy.rawFunction); - expect([11], copy.framesOmitted); - expect(['ab'], copy.preContext); - expect(['bb'], copy.postContext); - expect({'key1': 'value1'}, copy.vars); - }); - }); -} diff --git a/dart/test/protocol/sentry_stack_trace_test.dart b/dart/test/protocol/sentry_stack_trace_test.dart deleted file mode 100644 index b8269f3bbb..0000000000 --- a/dart/test/protocol/sentry_stack_trace_test.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final sentryStackTrace = SentryStackTrace( - frames: [SentryStackFrame(absPath: 'abs')], - registers: {'key': 'value'}, - lang: 'de', - snapshot: true, - unknown: testUnknown, - ); - - final sentryStackTraceJson = { - 'frames': [ - {'abs_path': 'abs'} - ], - 'registers': {'key': 'value'}, - 'lang': 'de', - 'snapshot': true, - }; - sentryStackTraceJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = sentryStackTrace.toJson(); - - expect( - DeepCollectionEquality().equals(sentryStackTraceJson, json), - true, - ); - }); - test('fromJson', () { - final sentryStackTrace = SentryStackTrace.fromJson(sentryStackTraceJson); - final json = sentryStackTrace.toJson(); - - expect( - DeepCollectionEquality().equals(sentryStackTraceJson, json), - true, - ); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryStackTrace; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect(data.toJson(), copy.toJson()); - }); - - test('copyWith takes new values', () { - final data = sentryStackTrace; - - final frames = [SentryStackFrame(absPath: 'abs1')]; - final registers = {'key1': 'value1'}; -// ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - frames: frames, - registers: registers, - ); - - expect( - ListEquality().equals(frames, copy.frames), - true, - ); - expect( - MapEquality().equals(registers, copy.registers), - true, - ); - }); - }); -} diff --git a/dart/test/protocol/sentry_trace_header_test.dart b/dart/test/protocol/sentry_trace_header_test.dart deleted file mode 100644 index 0743c03da8..0000000000 --- a/dart/test/protocol/sentry_trace_header_test.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - final _traceId = SentryId.newId(); - final _spanId = SpanId.newId(); - test('header adds 1 to sampled', () { - final header = SentryTraceHeader(_traceId, _spanId, sampled: true); - - expect(header.value, '$_traceId-$_spanId-1'); - }); - - test('header adds 0 to not sampled', () { - final header = SentryTraceHeader(_traceId, _spanId, sampled: false); - - expect(header.value, '$_traceId-$_spanId-0'); - }); - - test('header does not add sampled if no sampled decision', () { - final header = SentryTraceHeader(_traceId, _spanId); - - expect(header.value, '$_traceId-$_spanId'); - }); - - test('header return its name', () { - final header = SentryTraceHeader(_traceId, _spanId); - - expect(header.name, 'sentry-trace'); - }); - - test('invalid header throws $InvalidSentryTraceHeaderException', () { - Object? exception; - try { - SentryTraceHeader.fromTraceHeader('invalidHeader'); - } catch (error) { - exception = error; - } - - expect(exception is InvalidSentryTraceHeaderException, true); - }); -} diff --git a/dart/test/protocol/sentry_transaction_info_test.dart b/dart/test/protocol/sentry_transaction_info_test.dart deleted file mode 100644 index 31438d820c..0000000000 --- a/dart/test/protocol/sentry_transaction_info_test.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - group('$SentryTransactionInfo', () { - final info = SentryTransactionInfo( - 'component', - unknown: testUnknown, - ); - - final json = {'source': 'component'}; - json.addAll(testUnknown); - - test('returns source', () { - expect(info.source, 'component'); - }); - - test('toJson has source', () { - expect(info.toJson(), json); - }); - - test('fromJson has source', () { - final info = SentryTransactionInfo.fromJson({'source': 'component'}); - expect(info.source, 'component'); - }); - }); -} diff --git a/dart/test/protocol/sentry_user_test.dart b/dart/test/protocol/sentry_user_test.dart deleted file mode 100644 index 6079e9be32..0000000000 --- a/dart/test/protocol/sentry_user_test.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; - -void main() { - final sentryUser = SentryUser( - id: 'id', - username: 'username', - email: 'email', - ipAddress: 'ipAddress', - data: {'key': 'value'}, - unknown: testUnknown, - ); - - final sentryUserJson = { - 'id': 'id', - 'username': 'username', - 'email': 'email', - 'ip_address': 'ipAddress', - 'data': {'key': 'value'}, - }; - sentryUserJson.addAll(testUnknown); - - group('json', () { - test('toJson', () { - final json = sentryUser.toJson(); - - print("$json"); - - expect( - DeepCollectionEquality().equals(sentryUserJson, json), - true, - ); - }); - test('fromJson', () { - final sentryUser = SentryUser.fromJson(sentryUserJson); - final json = sentryUser.toJson(); - - expect( - DeepCollectionEquality().equals(sentryUserJson, json), - true, - ); - }); - - test('toJson only serialises non-null values', () { - var data = SentryUser(id: 'id'); - - var json = data.toJson(); - - expect(json.containsKey('id'), true); - expect(json.containsKey('username'), false); - expect(json.containsKey('email'), false); - expect(json.containsKey('ip_address'), false); - expect(json.containsKey('extras'), false); - - data = SentryUser(ipAddress: 'ip'); - - json = data.toJson(); - - expect(json.containsKey('id'), false); - expect(json.containsKey('username'), false); - expect(json.containsKey('email'), false); - expect(json.containsKey('ip_address'), true); - expect(json.containsKey('extras'), false); - }); - }); - - group('copyWith', () { - test('copyWith keeps unchanged', () { - final data = sentryUser; - - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith(); - - expect(data.toJson(), copy.toJson()); - }); - - test('copyWith takes new values', () { - final data = sentryUser; - // ignore: deprecated_member_use_from_same_package - final copy = data.copyWith( - id: 'id1', - username: 'username1', - email: 'email1', - ipAddress: 'ipAddress1', - data: {'key1': 'value1'}, - ); - - expect('id1', copy.id); - expect('username1', copy.username); - expect('email1', copy.email); - expect('ipAddress1', copy.ipAddress); - expect({'key1': 'value1'}, copy.data); - }); - }); -} diff --git a/dart/test/protocol/sentry_view_hierarchy_element_test.dart b/dart/test/protocol/sentry_view_hierarchy_element_test.dart deleted file mode 100644 index 1d6e753acd..0000000000 --- a/dart/test/protocol/sentry_view_hierarchy_element_test.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - group('json', () { - test('toJson with children', () { - final element = SentryViewHierarchyElement( - 'RenderObjectToWidgetAdapter', - depth: 1, - identifier: 'RenderView#a2216', - width: 100, - height: 200, - x: 100, - y: 50, - z: 30, - visible: true, - alpha: 90, - extra: {'key': 'value'}, - ); - final element2 = SentryViewHierarchyElement( - 'SentryScreenshotWidget', - depth: 2, - ); - element.children.add(element2); - - final map = element.toJson(); - - expect(map, { - 'type': 'RenderObjectToWidgetAdapter', - 'depth': 1, - 'identifier': 'RenderView#a2216', - 'children': [ - { - 'type': 'SentryScreenshotWidget', - 'depth': 2, - }, - ], - 'width': 100, - 'height': 200, - 'x': 100, - 'y': 50, - 'z': 30, - 'visible': true, - 'alpha': 90, - 'key': 'value', - }); - }); - - test('toJson no children', () { - final element = SentryViewHierarchyElement( - 'RenderObjectToWidgetAdapter', - depth: 1, - identifier: 'RenderView#a2216', - ); - - final map = element.toJson(); - - expect(map, { - 'type': 'RenderObjectToWidgetAdapter', - 'depth': 1, - 'identifier': 'RenderView#a2216', - }); - }); - }); -} diff --git a/dart/test/protocol/sentry_view_hierarchy_test.dart b/dart/test/protocol/sentry_view_hierarchy_test.dart deleted file mode 100644 index cde2124682..0000000000 --- a/dart/test/protocol/sentry_view_hierarchy_test.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - group('json', () { - test('toJson with children', () { - final element = SentryViewHierarchyElement( - 'RenderObjectToWidgetAdapter', - depth: 1, - identifier: 'RenderView#a2216', - ); - - final element2 = SentryViewHierarchyElement( - 'SentryScreenshotWidget', - depth: 2, - ); - element.children.add(element2); - - final viewHierrchy = SentryViewHierarchy('flutter'); - viewHierrchy.windows.add(element); - - final map = viewHierrchy.toJson(); - - expect(map, { - 'rendering_system': 'flutter', - 'windows': [ - { - 'type': 'RenderObjectToWidgetAdapter', - 'depth': 1, - 'identifier': 'RenderView#a2216', - 'children': [ - { - 'type': 'SentryScreenshotWidget', - 'depth': 2, - }, - ] - }, - ], - }); - }); - - test('toJson no children', () { - final viewHierrchy = SentryViewHierarchy('flutter'); - - final map = viewHierrchy.toJson(); - - expect(map, { - 'rendering_system': 'flutter', - }); - }); - }); -} diff --git a/dart/test/protocol/span_id_test.dart b/dart/test/protocol/span_id_test.dart deleted file mode 100644 index cefecec576..0000000000 --- a/dart/test/protocol/span_id_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - test('empty serializes to 0000000000000000', () { - expect(SpanId.empty().toString(), '0000000000000000'); - }); - - test('fromId serializes to 976e0cd945864f60', () { - expect(SpanId.fromId('976e0cd945864f60').toString(), '976e0cd945864f60'); - }); - - test('newId generates new id', () { - expect(SpanId.newId().toString(), isNotNull); - }); - - test('equality check matches for same id', () { - final id1 = SpanId.fromId('976e0cd945864f60'); - final id2 = SpanId.fromId('976e0cd945864f60'); - - expect(id1, id2); - }); -} diff --git a/dart/test/protocol/span_status_test.dart b/dart/test/protocol/span_status_test.dart deleted file mode 100644 index 87588feb07..0000000000 --- a/dart/test/protocol/span_status_test.dart +++ /dev/null @@ -1,138 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - test('SpanStatus ok', () { - expect(SpanStatus.ok().toString(), 'ok'); - }); - - test('SpanStatus cancelled', () { - expect(SpanStatus.cancelled().toString(), 'cancelled'); - }); - - test('SpanStatus internalError', () { - expect(SpanStatus.internalError().toString(), 'internal_error'); - }); - - test('SpanStatus unknown', () { - expect(SpanStatus.unknown().toString(), 'unknown'); - }); - - test('SpanStatus unknownError', () { - expect(SpanStatus.unknownError().toString(), 'unknown_error'); - }); - - test('SpanStatus invalidArgument', () { - expect(SpanStatus.invalidArgument().toString(), 'invalid_argument'); - }); - - test('SpanStatus deadlineExceeded', () { - expect(SpanStatus.deadlineExceeded().toString(), 'deadline_exceeded'); - }); - - test('SpanStatus notFound', () { - expect(SpanStatus.notFound().toString(), 'not_found'); - }); - - test('SpanStatus alreadyExists', () { - expect(SpanStatus.alreadyExists().toString(), 'already_exists'); - }); - - test('SpanStatus permissionDenied', () { - expect(SpanStatus.permissionDenied().toString(), 'permission_denied'); - }); - - test('SpanStatus resourceExhausted', () { - expect(SpanStatus.resourceExhausted().toString(), 'resource_exhausted'); - }); - - test('SpanStatus failedPrecondition', () { - expect(SpanStatus.failedPrecondition().toString(), 'failed_precondition'); - }); - - test('SpanStatus aborted', () { - expect(SpanStatus.aborted().toString(), 'aborted'); - }); - - test('SpanStatus outOfRange', () { - expect(SpanStatus.outOfRange().toString(), 'out_of_range'); - }); - - test('SpanStatus unimplemented', () { - expect(SpanStatus.unimplemented().toString(), 'unimplemented'); - }); - - test('SpanStatus unavailable', () { - expect(SpanStatus.unavailable().toString(), 'unavailable'); - }); - - test('SpanStatus dataLoss', () { - expect(SpanStatus.dataLoss().toString(), 'data_loss'); - }); - - test('SpanStatus unauthenticated', () { - expect(SpanStatus.unauthenticated().toString(), 'unauthenticated'); - }); - - test('fromHttpStatusCode returns ok if 200 to 299', () { - expect(SpanStatus.fromHttpStatusCode(200), SpanStatus.ok()); - expect(SpanStatus.fromHttpStatusCode(299), SpanStatus.ok()); - }); - - test('fromHttpStatusCode returns cancelled if 499', () { - expect(SpanStatus.fromHttpStatusCode(499), SpanStatus.cancelled()); - }); - - test('fromHttpStatusCode returns unknown if 500', () { - expect(SpanStatus.fromHttpStatusCode(500), SpanStatus.unknown()); - }); - - test('fromHttpStatusCode returns invalid argument if 500', () { - expect(SpanStatus.fromHttpStatusCode(400), SpanStatus.invalidArgument()); - }); - - test('fromHttpStatusCode returns invalid argument if 504', () { - expect(SpanStatus.fromHttpStatusCode(504), SpanStatus.deadlineExceeded()); - }); - - test('fromHttpStatusCode returns not found if 404', () { - expect(SpanStatus.fromHttpStatusCode(404), SpanStatus.notFound()); - }); - - test('fromHttpStatusCode returns already exists if 409', () { - expect(SpanStatus.fromHttpStatusCode(409), SpanStatus.alreadyExists()); - }); - - test('fromHttpStatusCode returns permissionDenied if 403', () { - expect(SpanStatus.fromHttpStatusCode(403), SpanStatus.permissionDenied()); - }); - - test('fromHttpStatusCode returns resourceExhausted if 429', () { - expect(SpanStatus.fromHttpStatusCode(429), SpanStatus.resourceExhausted()); - }); - - test('fromHttpStatusCode returns unimplemented if 501', () { - expect(SpanStatus.fromHttpStatusCode(501), SpanStatus.unimplemented()); - }); - - test('fromHttpStatusCode returns unavailable if 503', () { - expect(SpanStatus.fromHttpStatusCode(503), SpanStatus.unavailable()); - }); - - test('fromHttpStatusCode returns unauthenticated if 401', () { - expect(SpanStatus.fromHttpStatusCode(401), SpanStatus.unauthenticated()); - }); - - test('fromHttpStatusCode returns unknownError if not found', () { - expect(SpanStatus.fromHttpStatusCode(100), SpanStatus.unknownError()); - }); - - test('fromHttpStatusCode returns fallback if not found', () { - expect( - SpanStatus.fromHttpStatusCode( - 101, - fallback: SpanStatus.aborted(), - ), - SpanStatus.aborted()); - }); -} diff --git a/dart/test/recursive_exception_cause_extractor_test.dart b/dart/test/recursive_exception_cause_extractor_test.dart deleted file mode 100644 index 9eeaf63697..0000000000 --- a/dart/test/recursive_exception_cause_extractor_test.dart +++ /dev/null @@ -1,189 +0,0 @@ -import 'package:sentry/src/exception_cause.dart'; -import 'package:sentry/src/exception_cause_extractor.dart'; -import 'package:sentry/src/recursive_exception_cause_extractor.dart'; -import 'package:sentry/src/protocol/mechanism.dart'; -import 'package:sentry/src/throwable_mechanism.dart'; -import 'package:test/test.dart'; -import 'test_utils.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('flatten', () { - final errorC = ExceptionC(); - final errorB = ExceptionB(errorC); - final errorA = ExceptionA(errorB); - - fixture.options.addExceptionCauseExtractor( - ExceptionACauseExtractor(false), - ); - - fixture.options.addExceptionCauseExtractor( - ExceptionBCauseExtractor(), - ); - - final sut = fixture.getSut(); - - final flattened = sut.flatten(errorA, null); - final actual = flattened.map((exceptionCause) => exceptionCause.exception); - expect(actual, [errorA, errorB, errorC]); - }); - - test('parent (source) references', () { - final errorC = ExceptionC(); - final errorB = ExceptionB(errorC); - final errorA = ExceptionA(errorB); - - fixture.options.addExceptionCauseExtractor( - ExceptionACauseExtractor(false), - ); - - fixture.options.addExceptionCauseExtractor( - ExceptionBCauseExtractor(), - ); - - final sut = fixture.getSut(); - - final flattened = sut.flatten(errorA, null); - final actual = - flattened.map((exceptionCause) => exceptionCause.source).toList(); - expect(actual, [null, "other", "anotherOther"]); - }); - - test('flatten breaks circularity', () { - final a = ExceptionCircularA(); - final b = ExceptionCircularB(); - a.other = b; - b.other = a; - - fixture.options.addExceptionCauseExtractor( - ExceptionCircularAExtractor(), - ); - - fixture.options.addExceptionCauseExtractor( - ExceptionCircularBExtractor(), - ); - - final sut = fixture.getSut(); - - final flattened = sut.flatten(a, null); - final actual = flattened.map((exceptionCause) => exceptionCause.exception); - - expect(actual, [a, b]); - }); - - test('flatten preserves throwable mechanism', () { - final errorC = ExceptionC(); - final errorB = ExceptionB(errorC); - final errorA = ExceptionA(errorB); - - fixture.options.addExceptionCauseExtractor( - ExceptionACauseExtractor(false), - ); - - fixture.options.addExceptionCauseExtractor( - ExceptionBCauseExtractor(), - ); - - final mechanism = Mechanism(type: "foo"); - final throwableMechanism = ThrowableMechanism(mechanism, errorA); - - final sut = fixture.getSut(); - final flattened = sut.flatten(throwableMechanism, null); - - final actual = flattened.map((exceptionCause) => exceptionCause.exception); - expect(actual, [throwableMechanism, errorB, errorC]); - }); - - test('throw during extractions is handled', () { - final errorB = ExceptionB(null); - final errorA = ExceptionA(errorB); - - fixture.options.addExceptionCauseExtractor( - ExceptionACauseExtractor(true), - ); - - fixture.options.addExceptionCauseExtractor( - ExceptionBCauseExtractor(), - ); - - fixture.options.automatedTestMode = false; - final sut = fixture.getSut(); - - final flattened = sut.flatten(errorA, null); - final actual = flattened.map((exceptionCause) => exceptionCause.exception); - - expect(actual, [errorA]); - }); -} - -class Fixture { - final options = defaultTestOptions(); - - RecursiveExceptionCauseExtractor getSut() { - return RecursiveExceptionCauseExtractor(options); - } -} - -class ExceptionA { - ExceptionA(this.other); - final ExceptionB? other; -} - -class ExceptionB { - ExceptionB(this.anotherOther); - final ExceptionC? anotherOther; -} - -class ExceptionC { - // I am empty inside -} - -class ExceptionACauseExtractor extends ExceptionCauseExtractor { - ExceptionACauseExtractor(this.throwing); - - final bool throwing; - - @override - ExceptionCause? cause(ExceptionA error) { - if (throwing) { - throw StateError("Unexpected exception"); - } - return ExceptionCause(error.other, null, source: "other"); - } -} - -class ExceptionBCauseExtractor extends ExceptionCauseExtractor { - @override - ExceptionCause? cause(ExceptionB error) { - return ExceptionCause(error.anotherOther, null, source: "anotherOther"); - } -} - -class ExceptionCircularA { - ExceptionCircularB? other; -} - -class ExceptionCircularB { - ExceptionCircularA? other; -} - -class ExceptionCircularAExtractor - extends ExceptionCauseExtractor { - @override - ExceptionCause? cause(ExceptionCircularA error) { - return ExceptionCause(error.other, null); - } -} - -class ExceptionCircularBExtractor - extends ExceptionCauseExtractor { - @override - ExceptionCause? cause(ExceptionCircularB error) { - return ExceptionCause(error.other, null); - } -} diff --git a/dart/test/scope_test.dart b/dart/test/scope_test.dart deleted file mode 100644 index a7824baac5..0000000000 --- a/dart/test/scope_test.dart +++ /dev/null @@ -1,920 +0,0 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'mocks/mock_hub.dart'; -import 'mocks/mock_scope_observer.dart'; -import 'test_utils.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('sets $SentryLevel', () { - final sut = fixture.getSut(); - - sut.level = SentryLevel.debug; - - expect(sut.level, SentryLevel.debug); - }); - - test('sets transaction', () { - final sut = fixture.getSut(); - - sut.transaction = 'test'; - - expect(sut.transaction, 'test'); - }); - - test('sets transaction overwrites span name', () { - final sut = fixture.getSut(); - - sut.span = fixture.sentryTracer; - sut.transaction = 'test'; - - expect(sut.transaction, 'test'); - expect((sut.span as SentryTracer).name, 'test'); - }); - - test('sets span overwrites transaction name', () { - final sut = fixture.getSut(); - - sut.span = fixture.sentryTracer; - - expect(sut.transaction, 'name'); - expect((sut.span as SentryTracer).name, 'name'); - }); - - test('removing span resets transaction if not set separately', () { - final sut = fixture.getSut(); - - sut.span = fixture.sentryTracer; - sut.span = null; - - expect(sut.transaction, isNull); - }); - - test('removing span does not reset transaction if set separately', () { - final sut = fixture.getSut(); - - sut.transaction = 'test'; - sut.span = fixture.sentryTracer; - sut.span = null; - - expect(sut.transaction, 'test'); - }); - - test('sets $SentryUser', () { - final sut = fixture.getSut(); - - final user = SentryUser(id: 'test'); - sut.setUser(user); - - expect(sut.user, user); - }); - - test('sets fingerprint', () { - final sut = fixture.getSut(); - - final fingerprints = ['test']; - sut.fingerprint = fingerprints; - - expect(sut.fingerprint, fingerprints); - }); - - test('sets replay ID', () { - final sut = fixture.getSut(); - - sut.replayId = SentryId.fromId('1'); - - expect(sut.replayId, SentryId.fromId('1')); - }); - - test('adds $Breadcrumb', () { - final sut = fixture.getSut(); - - final breadcrumb = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - sut.addBreadcrumb(breadcrumb); - - expect(sut.breadcrumbs.last, breadcrumb); - }); - - test('beforeBreadcrumb called with user provided hint', () { - Hint? actual; - BeforeBreadcrumbCallback bb = (_, hint) { - actual = hint; - return null; - }; - final sut = fixture.getSut( - beforeBreadcrumbCallback: bb, - ); - - final breadcrumb = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - final hint = Hint.withMap({'user-name': 'joe dirt'}); - sut.addBreadcrumb(breadcrumb, hint: hint); - - expect(actual?.get('user-name'), 'joe dirt'); - }); - - test('Executes and drops $Breadcrumb', () { - final sut = fixture.getSut( - beforeBreadcrumbCallback: fixture.beforeBreadcrumbCallback, - ); - - final breadcrumb = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - sut.addBreadcrumb(breadcrumb); - - expect(sut.breadcrumbs.length, 0); - }); - - test('Executes and mutates $Breadcrumb', () { - final sut = fixture.getSut( - beforeBreadcrumbCallback: fixture.beforeBreadcrumbMutateCallback, - ); - - final breadcrumb = Breadcrumb( - message: 'message', - timestamp: DateTime.utc(2019), - ); - sut.addBreadcrumb(breadcrumb); - - expect(sut.breadcrumbs.first.message, 'new message'); - }); - - test('adds $EventProcessor', () { - final sut = fixture.getSut(); - - sut.addEventProcessor(fixture.processor); - - expect(sut.eventProcessors.last, isA()); - }); - - test('respects max $Breadcrumb', () { - final maxBreadcrumbs = 2; - final sut = fixture.getSut(maxBreadcrumbs: maxBreadcrumbs); - - final breadcrumb1 = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - final breadcrumb2 = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - final breadcrumb3 = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - sut.addBreadcrumb(breadcrumb1); - sut.addBreadcrumb(breadcrumb2); - sut.addBreadcrumb(breadcrumb3); - - expect(sut.breadcrumbs.length, maxBreadcrumbs); - }); - - test('rotates $Breadcrumb', () { - final sut = fixture.getSut(maxBreadcrumbs: 2); - - final breadcrumb1 = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - final breadcrumb2 = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - final breadcrumb3 = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - sut.addBreadcrumb(breadcrumb1); - sut.addBreadcrumb(breadcrumb2); - sut.addBreadcrumb(breadcrumb3); - - expect(sut.breadcrumbs.first, breadcrumb2); - - expect(sut.breadcrumbs.last, breadcrumb3); - }); - - test('empty $Breadcrumb list', () { - final maxBreadcrumbs = 0; - final sut = fixture.getSut(maxBreadcrumbs: maxBreadcrumbs); - - final breadcrumb1 = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - sut.addBreadcrumb(breadcrumb1); - - expect(sut.breadcrumbs.length, maxBreadcrumbs); - }); - - test('clears $Breadcrumb list', () { - final sut = fixture.getSut(); - - final breadcrumb1 = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - sut.addBreadcrumb(breadcrumb1); - sut.clear(); - - expect(sut.breadcrumbs.length, 0); - }); - - test('adds $SentryAttachment', () { - final sut = fixture.getSut(); - - final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); - sut.addAttachment(attachment); - - expect(sut.attachments.last, attachment); - expect(sut.attachments.length, 1); - }); - - test('clear() removes all $SentryAttachment', () { - final sut = fixture.getSut(); - - final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); - sut.addAttachment(attachment); - expect(sut.attachments.length, 1); - sut.clear(); - - expect(sut.attachments.length, 0); - }); - - test('clearAttachments() removes all $SentryAttachment', () { - final sut = fixture.getSut(); - - final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); - sut.addAttachment(attachment); - expect(sut.attachments.length, 1); - sut.clearAttachments(); - - expect(sut.attachments.length, 0); - }); - - test('sets tag', () { - final sut = fixture.getSut(); - - sut.setTag('test', 'test'); - - expect(sut.tags['test'], 'test'); - }); - - test('removes tag', () { - final sut = fixture.getSut(); - - sut.setTag('test', 'test'); - sut.removeTag('test'); - - expect(sut.tags['test'], null); - }); - - test('sets extra', () { - final sut = fixture.getSut(); - - sut.setExtra('test', 'test'); - - expect(sut.extra['test'], 'test'); - }); - - test('removes extra', () { - final sut = fixture.getSut(); - - sut.setExtra('test', 'test'); - sut.removeExtra('test'); - - expect(sut.extra['test'], null); - }); - - test('clears $Scope', () { - final sut = fixture.getSut(); - - final breadcrumb1 = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - sut.addBreadcrumb(breadcrumb1); - - sut.level = SentryLevel.debug; - sut.transaction = 'test'; - sut.span = null; - sut.replayId = SentryId.newId(); - - final user = SentryUser(id: 'test'); - sut.setUser(user); - - final fingerprints = ['test']; - sut.fingerprint = fingerprints; - - sut.setTag('test', 'test'); - sut.setExtra('test', 'test'); - - sut.addEventProcessor(fixture.processor); - - sut.clear(); - - expect(sut.breadcrumbs.length, 0); - expect(sut.level, null); - expect(sut.transaction, null); - expect(sut.span, null); - expect(sut.user, null); - expect(sut.fingerprint.length, 0); - expect(sut.tags.length, 0); - expect(sut.extra.length, 0); - expect(sut.eventProcessors.length, 0); - expect(sut.replayId, isNull); - }); - - test('clones', () async { - final sut = fixture.getSut(); - - await sut.addBreadcrumb(Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - )); - sut.addAttachment(SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt')); - sut.span = NoOpSentrySpan(); - sut.level = SentryLevel.warning; - sut.replayId = SentryId.newId(); - await sut.setUser(SentryUser(id: 'id')); - await sut.setTag('key', 'vakye'); - await sut.setExtra('key', 'vakye'); - sut.transaction = 'transaction'; - - final clone = sut.clone(); - expect(sut.user, clone.user); - expect(sut.transaction, clone.transaction); - expect(sut.extra, clone.extra); - expect(sut.tags, clone.tags); - expect(sut.breadcrumbs, clone.breadcrumbs); - expect(sut.contexts, clone.contexts); - expect(sut.attachments, clone.attachments); - expect(sut.level, clone.level); - expect(ListEquality().equals(sut.fingerprint, clone.fingerprint), true); - expect( - ListEquality().equals(sut.eventProcessors, clone.eventProcessors), - true, - ); - expect(sut.span, clone.span); - expect(sut.replayId, clone.replayId); - }); - - test('clone does not additionally call observers', () async { - final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); - - await sut.setContexts("fixture-contexts-key", "fixture-contexts-value"); - await sut.removeContexts("fixture-contexts-key"); - await sut.setUser(SentryUser(username: "fixture-username")); - await sut.addBreadcrumb(Breadcrumb()); - await sut.clearBreadcrumbs(); - await sut.setExtra("fixture-extra-key", "fixture-extra-value"); - await sut.removeExtra("fixture-extra-key"); - await sut.setTag("fixture-tag-key", "fixture-tag-value"); - await sut.removeTag("fixture-tag-key"); - - sut.clone(); - - expect(1, fixture.mockScopeObserver.numberOfSetContextsCalls); - expect(1, fixture.mockScopeObserver.numberOfRemoveContextsCalls); - expect(1, fixture.mockScopeObserver.numberOfSetUserCalls); - expect(1, fixture.mockScopeObserver.numberOfAddBreadcrumbCalls); - expect(1, fixture.mockScopeObserver.numberOfClearBreadcrumbsCalls); - expect(1, fixture.mockScopeObserver.numberOfSetExtraCalls); - expect(1, fixture.mockScopeObserver.numberOfRemoveExtraCalls); - expect(1, fixture.mockScopeObserver.numberOfSetTagCalls); - expect(1, fixture.mockScopeObserver.numberOfRemoveTagCalls); - }); - - test('clone has disabled scope sync', () async { - final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); - final clone = sut.clone(); - - await clone.setContexts("fixture-contexts-key", "fixture-contexts-value"); - expect(0, fixture.mockScopeObserver.numberOfSetContextsCalls); - }); - - test('clone shares propagation context to maintain trace continuity', () { - final sut = fixture.getSut(); - - // Clone the scope - final clone = sut.clone(); - - // Verify the propagation context is shared (same instance) - expect(identical(sut.propagationContext, clone.propagationContext), true, - reason: 'Propagation context should be the same instance'); - expect(clone.propagationContext.traceId, sut.propagationContext.traceId); - }); - - group('Scope apply', () { - final scopeUser = SentryUser( - id: '800', - username: 'first-user', - email: 'first@user.lan', - ipAddress: '127.0.0.1', - data: const {'first-sign-in': '2020-01-01'}, - ); - - final breadcrumb = Breadcrumb(message: 'Authenticated'); - - test('apply context to event', () async { - final event = SentryEvent( - tags: const {'etag': '987'}, - extra: const {'e-infos': 'abc'}, - ); - final scope = Scope(defaultTestOptions()) - ..fingerprint = ['example-dart'] - ..transaction = '/example/app' - ..level = SentryLevel.warning - ..addEventProcessor(AddTagsEventProcessor({'page-locale': 'en-us'})); - - await scope.addBreadcrumb(breadcrumb); - await scope.setTag('build', '579'); - await scope.setExtra('company-name', 'Dart Inc'); - await scope.setContexts('theme', 'material'); - await scope.setContexts( - SentryFeatureFlags.type, - SentryFeatureFlags( - values: [SentryFeatureFlag(flag: 'foo', result: true)], - )); - await scope.setUser(scopeUser); - - final updatedEvent = await scope.applyToEvent(event, Hint()); - - expect(updatedEvent?.user, scopeUser); - expect(updatedEvent?.transaction, '/example/app'); - expect(updatedEvent?.fingerprint, ['example-dart']); - expect(updatedEvent?.breadcrumbs, [breadcrumb]); - expect(updatedEvent?.level, SentryLevel.warning); - expect(updatedEvent?.tags, - {'etag': '987', 'build': '579', 'page-locale': 'en-us'}); - expect( - updatedEvent?.extra, {'e-infos': 'abc', 'company-name': 'Dart Inc'}); - expect(updatedEvent?.contexts['theme'], {'value': 'material'}); - expect(updatedEvent?.contexts[SentryFeatureFlags.type]?.values.first.flag, - 'foo'); - expect( - updatedEvent?.contexts[SentryFeatureFlags.type]?.values.first.result, - true); - }); - - test('apply trace context to event with active span', () async { - final event = SentryEvent(); - final scope = Scope(defaultTestOptions())..span = fixture.sentryTracer; - - final updatedEvent = await scope.applyToEvent(event, Hint()); - - expect(updatedEvent?.contexts['trace'] is SentryTraceContext, isTrue); - }); - - test('apply trace context to event with propagation context', () async { - final event = SentryEvent(); - final event2 = SentryEvent(); - final scope = Scope(defaultTestOptions()); - - final updatedEvent = await scope.applyToEvent(event, Hint()); - - final traceContext = - updatedEvent?.contexts['trace'] as SentryTraceContext; - final spanId1 = traceContext.spanId; - expect(traceContext.traceId, scope.propagationContext.traceId); - - final updatedEvent2 = await scope.applyToEvent(event2, Hint()); - final traceContext2 = - updatedEvent2?.contexts['trace'] as SentryTraceContext; - final spanId2 = traceContext2.spanId; - - // trace contexts from the scope should always re-generate span ids - expect(spanId1, isNot(spanId2)); - }); - - test('should not apply the scope properties when event already has it ', - () async { - final eventUser = SentryUser(id: '123'); - final eventBreadcrumb = Breadcrumb(message: 'event-breadcrumb'); - - final event = SentryEvent( - transaction: '/event/transaction', - user: eventUser, - fingerprint: ['event-fingerprint'], - breadcrumbs: [eventBreadcrumb], - ); - final scope = Scope(defaultTestOptions()) - ..fingerprint = ['example-dart'] - ..transaction = '/example/app'; - - await scope.addBreadcrumb(breadcrumb); - await scope.setUser(scopeUser); - - final updatedEvent = await scope.applyToEvent(event, Hint()); - - expect(updatedEvent?.user, isNotNull); - expect(updatedEvent?.user?.id, eventUser.id); - expect(updatedEvent?.transaction, '/event/transaction'); - expect(updatedEvent?.fingerprint, ['event-fingerprint']); - expect(updatedEvent?.breadcrumbs, [eventBreadcrumb]); - }); - - test( - 'should not apply the scope.contexts values if the event already has it', - () async { - final event = SentryEvent( - contexts: Contexts( - device: SentryDevice(name: 'event-device'), - app: SentryApp(name: 'event-app'), - gpu: SentryGpu(name: 'event-gpu'), - runtimes: [SentryRuntime(name: 'event-runtime')], - browser: SentryBrowser(name: 'event-browser'), - operatingSystem: SentryOperatingSystem(name: 'event-os'), - ), - ); - final scope = Scope(defaultTestOptions()); - await scope.setContexts( - SentryDevice.type, - SentryDevice(name: 'context-device'), - ); - await scope.setContexts( - SentryApp.type, - SentryApp(name: 'context-app'), - ); - await scope.setContexts( - SentryGpu.type, - SentryGpu(name: 'context-gpu'), - ); - await scope.setContexts( - SentryRuntime.listType, - [SentryRuntime(name: 'context-runtime')], - ); - await scope.setContexts( - SentryBrowser.type, - SentryBrowser(name: 'context-browser'), - ); - await scope.setContexts( - SentryOperatingSystem.type, - SentryOperatingSystem(name: 'context-os'), - ); - - final updatedEvent = await scope.applyToEvent(event, Hint()); - - expect(updatedEvent?.contexts[SentryDevice.type].name, 'event-device'); - expect(updatedEvent?.contexts[SentryApp.type].name, 'event-app'); - expect(updatedEvent?.contexts[SentryGpu.type].name, 'event-gpu'); - expect(updatedEvent?.contexts[SentryRuntime.listType].first.name, - 'event-runtime'); - expect(updatedEvent?.contexts[SentryBrowser.type].name, 'event-browser'); - expect( - updatedEvent?.contexts[SentryOperatingSystem.type].name, 'event-os'); - }); - - test('should apply the scope.contexts values', () async { - final event = SentryEvent(); - final scope = Scope(defaultTestOptions()); - await scope.setContexts( - SentryDevice.type, SentryDevice(name: 'context-device')); - await scope.setContexts(SentryApp.type, SentryApp(name: 'context-app')); - await scope.setContexts(SentryGpu.type, SentryGpu(name: 'context-gpu')); - await scope.setContexts( - SentryRuntime.listType, [SentryRuntime(name: 'context-runtime')]); - await scope.setContexts( - SentryBrowser.type, SentryBrowser(name: 'context-browser')); - await scope.setContexts(SentryOperatingSystem.type, - SentryOperatingSystem(name: 'context-os')); - await scope.setContexts('theme', 'material'); - await scope.setContexts('version', 9); - await scope.setContexts('location', {'city': 'London'}); - await scope.setContexts('items', [1, 2, 3]); - - final updatedEvent = await scope.applyToEvent(event, Hint()); - - expect(updatedEvent?.contexts[SentryDevice.type].name, 'context-device'); - expect(updatedEvent?.contexts[SentryApp.type].name, 'context-app'); - expect(updatedEvent?.contexts[SentryGpu.type].name, 'context-gpu'); - expect( - updatedEvent?.contexts[SentryRuntime.listType].first.name, - 'context-runtime', - ); - expect( - updatedEvent?.contexts[SentryBrowser.type].name, 'context-browser'); - expect(updatedEvent?.contexts[SentryOperatingSystem.type].name, - 'context-os'); - expect(updatedEvent?.contexts['theme']['value'], 'material'); - expect(updatedEvent?.contexts['version']['value'], 9); - expect(updatedEvent?.contexts['location'], {'city': 'London'}); - final items = updatedEvent?.contexts['items']; - expect(items['value'], [1, 2, 3]); - }); - - test('should apply the scope level', () async { - final event = SentryEvent(level: SentryLevel.warning); - final scope = Scope(defaultTestOptions())..level = SentryLevel.error; - - final updatedEvent = await scope.applyToEvent(event, Hint()); - - expect(updatedEvent?.level, SentryLevel.error); - }); - - test('should apply the scope transaction from the span', () async { - final event = SentryEvent(); - final scope = Scope(defaultTestOptions())..span = fixture.sentryTracer; - - final updatedEvent = await scope.applyToEvent(event, Hint()); - - expect(updatedEvent?.transaction, 'name'); - }); - - test('should not apply breadcrumbs if feedback event', () async { - final feedback = SentryFeedback( - message: 'fixture-message', - ); - final feedbackEvent = SentryEvent( - type: 'feedback', - contexts: Contexts(feedback: feedback), - level: SentryLevel.info, - ); - final scope = Scope(defaultTestOptions()); - await scope.addBreadcrumb(Breadcrumb(message: 'fixture-breadcrumb')); - - final updatedEvent = await scope.applyToEvent(feedbackEvent, Hint()); - - expect(updatedEvent?.breadcrumbs, isNull); - }); - - test('should not apply extras if feedback event', () async { - final feedback = SentryFeedback( - message: 'fixture-message', - ); - final feedbackEvent = SentryEvent( - type: 'feedback', - contexts: Contexts(feedback: feedback), - level: SentryLevel.info, - ); - final scope = Scope(defaultTestOptions()); - await scope.setExtra('fixture-extra-key', 'fixture-extra-value'); - - final updatedEvent = await scope.applyToEvent(feedbackEvent, Hint()); - - expect(updatedEvent?.extra, isNull); - }); - }); - - test('event processor drops the event', () async { - final sut = fixture.getSut(); - - sut.addEventProcessor(fixture.processor); - - final event = SentryEvent(); - var newEvent = await sut.applyToEvent(event, Hint()); - - expect(newEvent, isNull); - }); - - test('should not apply fingerprint if transaction', () async { - var tr = SentryTransaction(fixture.sentryTracer); - final scope = Scope(defaultTestOptions())..fingerprint = ['test']; - - final updatedTr = await scope.applyToEvent(tr, Hint()); - - expect(updatedTr?.fingerprint, isNull); - }); - - test('should not apply level if transaction', () async { - var tr = SentryTransaction(fixture.sentryTracer); - final scope = Scope(defaultTestOptions())..level = SentryLevel.error; - - final updatedTr = await scope.applyToEvent(tr, Hint()); - - expect(updatedTr?.level, isNull); - }); - - test('apply sampled to trace', () async { - var tr = SentryTransaction(fixture.sentryTracer); - final scope = Scope(defaultTestOptions())..level = SentryLevel.error; - - final updatedTr = await scope.applyToEvent(tr, Hint()); - - expect(updatedTr?.contexts.trace?.sampled, isTrue); - }); - - test('addBreadcrumb should call scope observers', () async { - final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); - await sut.addBreadcrumb(Breadcrumb()); - - expect(true, fixture.mockScopeObserver.calledAddBreadcrumb); - }); - - test('addBreadcrumb passes processed breadcrumb to scope observers', - () async { - final sut = fixture.getSut( - scopeObserver: fixture.mockScopeObserver, - beforeBreadcrumbCallback: ( - Breadcrumb? breadcrumb, - Hint hint, - ) { - breadcrumb?.message = "modified"; - return breadcrumb; - }, - ); - await sut.addBreadcrumb(Breadcrumb()); - - expect(fixture.mockScopeObserver.addedBreadcrumbs[0].message, "modified"); - }); - - test('clearBreadcrumbs should call scope observers', () async { - final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); - await sut.clearBreadcrumbs(); - - expect(true, fixture.mockScopeObserver.calledClearBreadcrumbs); - }); - - test('removeContexts should call scope observers', () async { - final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); - await sut.removeContexts('fixture-key'); - - expect(true, fixture.mockScopeObserver.calledRemoveContexts); - }); - - test('removeExtra should call scope observers', () async { - final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); - await sut.removeExtra('fixture-key'); - - expect(true, fixture.mockScopeObserver.calledRemoveExtra); - }); - - test('removeTag should call scope observers', () async { - final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); - await sut.removeTag('fixture-key'); - - expect(true, fixture.mockScopeObserver.calledRemoveTag); - }); - - test('setContexts should call scope observers', () async { - final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); - await sut.setContexts('fixture-key', 'fixture-value'); - - expect(true, fixture.mockScopeObserver.calledSetContexts); - }); - - test('setExtra should call scope observers', () async { - final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); - await sut.setExtra('fixture-key', 'fixture-value'); - - expect(true, fixture.mockScopeObserver.calledSetExtra); - }); - - test('setTag should call scope observers', () async { - final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); - await sut.setTag('fixture-key', 'fixture-value'); - - expect(true, fixture.mockScopeObserver.calledSetTag); - }); - - test('setUser should call scope observers', () async { - final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); - await sut.setUser(null); - - expect(true, fixture.mockScopeObserver.calledSetUser); - }); - - group("Scope exceptions", () { - test("addBreadcrumb with beforeBreadcrumb error handled ", () async { - final exception = Exception("before breadcrumb exception"); - - fixture.options.automatedTestMode = false; - final sut = fixture.getSut( - beforeBreadcrumbCallback: ( - Breadcrumb? breadcrumb, - Hint hint, - ) { - throw exception; - }, - debug: true); - - final breadcrumb = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - - await sut.addBreadcrumb(breadcrumb); - - expect(fixture.loggedException, exception); - expect(fixture.loggedLevel, SentryLevel.error); - }); - - test("clone with beforeBreadcrumb error handled ", () async { - var numberOfBeforeBreadcrumbCalls = 0; - final exception = Exception("before breadcrumb exception"); - - fixture.options.automatedTestMode = false; - final sut = fixture.getSut( - beforeBreadcrumbCallback: ( - Breadcrumb? breadcrumb, - Hint hint, - ) { - if (numberOfBeforeBreadcrumbCalls > 0) { - throw exception; - } - numberOfBeforeBreadcrumbCalls += 1; - return breadcrumb; - }, - debug: true); - - final breadcrumb = Breadcrumb( - message: 'test log', - timestamp: DateTime.utc(2019), - ); - await sut.addBreadcrumb(breadcrumb); - sut.clone(); - - expect(fixture.loggedException, exception); - expect(fixture.loggedLevel, SentryLevel.error); - }); - }); - - // addBreadcrumb - // clone -} - -class Fixture { - final mockScopeObserver = MockScopeObserver(); - - final options = defaultTestOptions(); - - final sentryTracer = SentryTracer( - SentryTransactionContext( - 'name', - 'op', - samplingDecision: SentryTracesSamplingDecision(true), - ), - MockHub(), - ); - - SentryLevel? loggedLevel; - Object? loggedException; - - Scope getSut({ - int maxBreadcrumbs = 100, - BeforeBreadcrumbCallback? beforeBreadcrumbCallback, - ScopeObserver? scopeObserver, - bool debug = false, - }) { - options.maxBreadcrumbs = maxBreadcrumbs; - options.beforeBreadcrumb = beforeBreadcrumbCallback; - options.debug = debug; - options.log = mockLogger; - - if (scopeObserver != null) { - options.addScopeObserver(scopeObserver); - } - return Scope(options); - } - - EventProcessor get processor => DropAllEventProcessor(); - - Breadcrumb? beforeBreadcrumbCallback(Breadcrumb? breadcrumb, Hint hint) => - null; - - Breadcrumb? beforeBreadcrumbMutateCallback( - Breadcrumb? breadcrumb, Hint hint) { - breadcrumb?.message = 'new message'; - return breadcrumb; - } - - void mockLogger( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - loggedLevel = level; - loggedException = exception; - } -} - -class AddTagsEventProcessor implements EventProcessor { - final Map tags; - - AddTagsEventProcessor(this.tags); - - @override - SentryEvent? apply(SentryEvent event, Hint hint) { - return event..tags?.addAll(tags); - } -} diff --git a/dart/test/sdk_lifecycle_hooks_test.dart b/dart/test/sdk_lifecycle_hooks_test.dart deleted file mode 100644 index 4ba5571afa..0000000000 --- a/dart/test/sdk_lifecycle_hooks_test.dart +++ /dev/null @@ -1,111 +0,0 @@ -// This tests that the base functionality of the hooks function correctly. - -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import 'test_utils.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - group('SdkLifecycleRegistry', () { - test('registers callback for given event type', () { - final registry = fixture.getSut(); - final cb = (OnBeforeSendEvent _) {}; - - registry.registerCallback(cb); - - expect(registry.lifecycleCallbacks[OnBeforeSendEvent], contains(cb)); - }); - - test('removes previously registered callback', () { - final registry = fixture.getSut(); - final cb = (OnBeforeSendEvent _) {}; - - registry.registerCallback(cb); - registry.removeCallback(cb); - - final callbacks = registry.lifecycleCallbacks[OnBeforeSendEvent]; - expect(callbacks, isNotNull); - expect(callbacks, isEmpty); - }); - - test('dispatch executes registered synchronous callback', () async { - final registry = fixture.getSut(); - var executed = false; - final cb = (OnBeforeSendEvent _) { - executed = true; - }; - - registry.registerCallback(cb); - - await registry.dispatchCallback( - OnBeforeSendEvent(SentryEvent(), Hint()), - ); - - expect(executed, isTrue); - }); - - test('dispatch executes registered asynchronous callback', () async { - final registry = fixture.getSut(); - var executed = false; - final cb = (OnBeforeSendEvent _) async { - await Future.delayed(Duration.zero); - executed = true; - }; - - registry.registerCallback(cb); - - await registry.dispatchCallback( - OnBeforeSendEvent(SentryEvent(), Hint()), - ); - - expect(executed, isTrue); - }); - - test('dispatch does not execute callback after removal', () async { - final registry = fixture.getSut(); - var executed = false; - final cb = (OnBeforeSendEvent _) { - executed = true; - }; - - registry.registerCallback(cb); - registry.removeCallback(cb); - - await registry.dispatchCallback( - OnBeforeSendEvent(SentryEvent(), Hint()), - ); - - expect(executed, isFalse); - }); - - test('dispatch handles exceptions thrown in the callback', () async { - fixture.options.automatedTestMode = false; - final registry = fixture.getSut(); - final cb = (OnBeforeSendEvent _) { - throw StateError('failure in callback'); - }; - - registry.registerCallback(cb); - - expect( - () async => registry.dispatchCallback( - OnBeforeSendEvent(SentryEvent(), Hint()), - ), - returnsNormally); - }); - }); -} - -class Fixture { - final SentryOptions options = defaultTestOptions(); - - SdkLifecycleRegistry getSut() { - return SdkLifecycleRegistry(options); - } -} diff --git a/dart/test/sentry_attachment_io_test.dart b/dart/test/sentry_attachment_io_test.dart deleted file mode 100644 index 1dd1850f41..0000000000 --- a/dart/test/sentry_attachment_io_test.dart +++ /dev/null @@ -1,32 +0,0 @@ -@TestOn('vm') -library; - -import 'dart:io'; - -import 'package:sentry/sentry_io.dart'; -import 'package:test/test.dart'; - -void main() { - group('$SentryAttachment ctor', () { - test('fromFile', () async { - final file = File('test_resources/testfile.txt'); - - final attachment = IoSentryAttachment.fromFile(file); - expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); - expect(attachment.contentType, isNull); - expect(attachment.filename, 'testfile.txt'); - await expectLater( - await attachment.bytes, [102, 111, 111, 32, 98, 97, 114]); - }); - - test('fromPath', () async { - final attachment = - IoSentryAttachment.fromPath('test_resources/testfile.txt'); - expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); - expect(attachment.contentType, isNull); - expect(attachment.filename, 'testfile.txt'); - await expectLater( - await attachment.bytes, [102, 111, 111, 32, 98, 97, 114]); - }); - }); -} diff --git a/dart/test/sentry_attachment_test.dart b/dart/test/sentry_attachment_test.dart deleted file mode 100644 index 060b8578f0..0000000000 --- a/dart/test/sentry_attachment_test.dart +++ /dev/null @@ -1,192 +0,0 @@ -import 'dart:typed_data'; - -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import 'mocks/mock_transport.dart'; -import 'test_utils.dart'; - -void main() { - group('$SentryAttachment ctor', () { - test('default', () async { - final attachment = SentryAttachment.fromLoader( - loader: () => Uint8List.fromList([0, 0, 0, 0]), - filename: 'test.txt', - ); - expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); - expect(attachment.contentType, isNull); - expect(attachment.filename, 'test.txt'); - await expectLater(await attachment.bytes, [0, 0, 0, 0]); - }); - - test('fromIntList', () async { - final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); - expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); - expect(attachment.contentType, isNull); - expect(attachment.filename, 'test.txt'); - await expectLater(await attachment.bytes, [0, 0, 0, 0]); - }); - - test('fromUint8List', () async { - final attachment = SentryAttachment.fromUint8List( - Uint8List.fromList([0, 0, 0, 0]), - 'test.txt', - ); - expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); - expect(attachment.contentType, isNull); - expect(attachment.filename, 'test.txt'); - await expectLater(await attachment.bytes, [0, 0, 0, 0]); - }); - - test('fromByteData', () async { - final attachment = SentryAttachment.fromByteData( - ByteData.sublistView(Uint8List.fromList([0, 0, 0, 0])), - 'test.txt', - ); - expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); - expect(attachment.contentType, isNull); - expect(attachment.filename, 'test.txt'); - await expectLater(await attachment.bytes, [0, 0, 0, 0]); - }); - }); - - group('$Scope $SentryAttachment tests', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('Sending with attachments', () async { - final sut = fixture.getSut(); - await sut.captureEvent(SentryEvent(), withScope: (scope) { - scope.addAttachment( - SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'), - ); - }); - expect(fixture.transport.envelopes.length, 1); - expect(fixture.transport.envelopes.first.items.length, 2); - final attachmentEnvelope = fixture.transport.envelopes.first.items[1]; - expect( - attachmentEnvelope.header.attachmentType, - SentryAttachment.typeAttachmentDefault, - ); - expect( - attachmentEnvelope.header.contentType, - isNull, - ); - expect( - attachmentEnvelope.header.fileName, - 'test.txt', - ); - await expectLater( - (await attachmentEnvelope.dataFactory()).length, - 4, - ); - }); - }); - - group('addToTransactions', () { - test('defaults to false fromLoader', () async { - final attachment = SentryAttachment.fromLoader( - loader: () => Uint8List.fromList([0, 0, 0, 0]), - filename: 'test.txt', - ); - - expect(attachment.addToTransactions, false); - }); - - test('defaults to false fromIntList', () async { - final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); - - expect(attachment.addToTransactions, false); - }); - - test('defaults to false fromUint8List', () async { - final attachment = SentryAttachment.fromUint8List( - Uint8List.fromList([0, 0, 0, 0]), - 'test.txt', - ); - - expect(attachment.addToTransactions, false); - }); - - test('defaults to false fromByteData', () async { - final attachment = SentryAttachment.fromByteData( - ByteData.sublistView(Uint8List.fromList([0, 0, 0, 0])), - 'test.txt', - ); - - expect(attachment.addToTransactions, false); - }); - - test('set fromLoader', () async { - final attachment = SentryAttachment.fromLoader( - loader: () => Uint8List.fromList([0, 0, 0, 0]), - filename: 'test.txt', - addToTransactions: true, - ); - - expect(attachment.addToTransactions, true); - }); - - test('defaults to false fromIntList', () async { - final attachment = SentryAttachment.fromIntList( - [0, 0, 0, 0], - 'test.txt', - addToTransactions: true, - ); - - expect(attachment.addToTransactions, true); - }); - - test('defaults to false fromUint8List', () async { - final attachment = SentryAttachment.fromUint8List( - Uint8List.fromList([0, 0, 0, 0]), - 'test.txt', - addToTransactions: true, - ); - - expect(attachment.addToTransactions, true); - }); - - test('defaults to false fromByteData', () async { - final attachment = SentryAttachment.fromByteData( - ByteData.sublistView(Uint8List.fromList([0, 0, 0, 0])), - 'test.txt', - addToTransactions: true, - ); - - expect(attachment.addToTransactions, true); - }); - - test('fromScreenshotData', () async { - final attachment = - SentryAttachment.fromScreenshotData(Uint8List.fromList([0, 0, 0, 0])); - expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); - expect(attachment.contentType, 'image/png'); - expect(attachment.filename, 'screenshot.png'); - expect(attachment.addToTransactions, false); - }); - - test('fromViewHierarchy', () async { - final view = SentryViewHierarchy('flutter'); - final attachment = SentryAttachment.fromViewHierarchy(view); - - expect(attachment.attachmentType, SentryAttachment.typeViewHierarchy); - expect(attachment.contentType, 'application/json'); - expect(attachment.filename, 'view-hierarchy.json'); - expect(attachment.addToTransactions, false); - }); - }); -} - -class Fixture { - MockTransport transport = MockTransport(); - - Hub getSut() { - final options = defaultTestOptions(); - options.transport = transport; - return Hub(options); - } -} diff --git a/dart/test/sentry_baggage_test.dart b/dart/test/sentry_baggage_test.dart deleted file mode 100644 index dccd656bd7..0000000000 --- a/dart/test/sentry_baggage_test.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:sentry/src/sentry_baggage.dart'; -import 'package:test/test.dart'; - -void main() { - group('$SentryBaggage', () { - test('reads from header string with spaces', () { - final headers = - 'userId = alice , serverNode = DF%2028,isProduction=false'; - final baggage = SentryBaggage.fromHeader(headers); - - expect(baggage.get('userId'), 'alice'); - expect(baggage.get('serverNode'), 'DF 28'); - expect(baggage.get('isProduction'), 'false'); - - expect(baggage.toHeaderString(), - 'userId=alice,serverNode=DF%2028,isProduction=false'); - }); - - test('decodes and encodes the headers', () { - final headers = - 'user%2Bid=alice,server%2Bnode=DF%2028,is%2Bproduction=false'; - final baggage = SentryBaggage.fromHeader(headers); - - expect(baggage.get('user+id'), 'alice'); - expect(baggage.get('server+node'), 'DF 28'); - expect(baggage.get('is+production'), 'false'); - - expect(baggage.toHeaderString(), - 'user%2Bid=alice,server%2Bnode=DF%2028,is%2Bproduction=false'); - }); - - test('reads from header list', () { - final headers = [ - 'userId = alice', - 'serverNode = DF%2028, isProduction = false' - ]; - final baggage = SentryBaggage.fromHeaderList(headers); - - expect(baggage.get('userId'), 'alice'); - expect(baggage.get('serverNode'), 'DF 28'); - expect(baggage.get('isProduction'), 'false'); - - expect(baggage.toHeaderString(), - 'userId=alice,serverNode=DF%2028,isProduction=false'); - }); - - test('reads from empty string', () { - final baggage = SentryBaggage.fromHeader(''); - - expect(baggage.toHeaderString(), isEmpty); - }); - - test('reads from blank string', () { - final baggage = SentryBaggage.fromHeader(' '); - - expect(baggage.toHeaderString(), isEmpty); - }); - - test('drops large values when above the limit', () { - final buffer = StringBuffer(); - for (int i = 0; i < 1000; i++) { - // 10 chars each loop - buffer.write('largeValue'); - } - - // max is 8192 - expect(buffer.length > 8192, isTrue); - final largeValue = buffer.toString(); - - final baggage = SentryBaggage.fromHeader( - 'smallValue=remains,largeValue=$largeValue,otherValue=kept'); - - expect(baggage.get('smallValue'), 'remains'); - expect(baggage.get('otherValue'), 'kept'); - expect(baggage.get('largeValue'), largeValue); - - expect(baggage.toHeaderString(), 'smallValue=remains,otherValue=kept'); - }); - - test('drops items when above the list member limit', () { - final buffer = StringBuffer(); - final match = StringBuffer(); - for (int i = 1; i <= 65; i++) { - final value = '$i=$i,'; - buffer.write(value); - // max is 64 - if (i <= 64) { - match.write(value); - } - } - final baggage = SentryBaggage.fromHeader(buffer.toString()); - - expect(baggage.toHeaderString(), - match.toString().substring(0, match.length - 1)); - }); - }); -} diff --git a/dart/test/sentry_browser_test.dart b/dart/test/sentry_browser_test.dart deleted file mode 100644 index cb83f510c9..0000000000 --- a/dart/test/sentry_browser_test.dart +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -@TestOn('browser') -library; - -import 'package:test/test.dart'; - -import 'test_utils.dart'; - -void main() { - runTest(isWeb: true); -} diff --git a/dart/test/sentry_client_lifecycle_test.dart b/dart/test/sentry_client_lifecycle_test.dart deleted file mode 100644 index 7d780d0cf9..0000000000 --- a/dart/test/sentry_client_lifecycle_test.dart +++ /dev/null @@ -1,195 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/platform/mock_platform.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:test/test.dart'; - -import 'mocks/mock_client_report_recorder.dart'; -import 'mocks/mock_log_batcher.dart'; -import 'mocks/mock_transport.dart'; -import 'sentry_client_test.dart'; -import 'test_utils.dart'; -import 'utils/url_details_test.dart'; - -void main() { - group('SDK lifecycle callbacks', () { - late Fixture fixture; - - setUp(() => fixture = Fixture()); - - group('Logs', () { - SentryLog givenLog() { - return SentryLog( - timestamp: DateTime.now(), - traceId: SentryId.newId(), - level: SentryLogLevel.info, - body: 'test', - attributes: { - 'attribute': SentryLogAttribute.string('value'), - }, - ); - } - - test('captureLog triggers OnBeforeCaptureLog', () async { - fixture.options.enableLogs = true; - fixture.options.environment = 'test-environment'; - fixture.options.release = 'test-release'; - - final log = givenLog(); - - final scope = Scope(fixture.options); - final span = MockSpan(); - scope.span = span; - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - client.lifeCycleRegistry.registerCallback((event) { - event.log.attributes['test'] = - SentryLogAttribute.string('test-value'); - }); - - await client.captureLog(log, scope: scope); - - final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; - expect(mockLogBatcher.addLogCalls.length, 1); - final capturedLog = mockLogBatcher.addLogCalls.first; - - expect(capturedLog.attributes['test']?.value, "test-value"); - expect(capturedLog.attributes['test']?.type, 'string'); - }); - }); - - group('SentryEvent', () { - test('captureEvent triggers OnBeforeSendEvent', () async { - fixture.options.enableLogs = true; - fixture.options.environment = 'test-environment'; - fixture.options.release = 'test-release'; - - final event = SentryEvent(); - - final scope = Scope(fixture.options); - final span = MockSpan(); - scope.span = span; - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - client.lifeCycleRegistry.registerCallback((event) { - event.event.release = '999'; - }); - - await client.captureEvent(event, scope: scope); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.release, '999'); - }); - }); - }); -} - -class Fixture { - final recorder = MockClientReportRecorder(); - final transport = MockTransport(); - - final options = defaultTestOptions() - ..platform = MockPlatform.iOS() - ..groupExceptions = true; - - late SentryTransactionContext _context; - late SentryTracer tracer; - - SentryLevel? loggedLevel; - Object? loggedException; - - SentryClient getSut({ - bool sendDefaultPii = false, - bool attachStacktrace = true, - bool attachThreads = false, - double? sampleRate, - BeforeSendCallback? beforeSend, - BeforeSendTransactionCallback? beforeSendTransaction, - BeforeSendCallback? beforeSendFeedback, - EventProcessor? eventProcessor, - bool provideMockRecorder = true, - bool debug = false, - Transport? transport, - }) { - options.tracesSampleRate = 1.0; - options.sendDefaultPii = sendDefaultPii; - options.attachStacktrace = attachStacktrace; - options.attachThreads = attachThreads; - options.sampleRate = sampleRate; - options.beforeSend = beforeSend; - options.beforeSendTransaction = beforeSendTransaction; - options.beforeSendFeedback = beforeSendFeedback; - options.debug = debug; - options.log = mockLogger; - - if (eventProcessor != null) { - options.addEventProcessor(eventProcessor); - } - - // Internally also creates a SentryClient instance - final hub = Hub(options); - _context = SentryTransactionContext( - 'name', - 'op', - ); - tracer = SentryTracer(_context, hub); - - // Reset transport - options.transport = transport ?? this.transport; - - // Again create SentryClient instance - final client = SentryClient(options); - - if (provideMockRecorder) { - options.recorder = recorder; - } - return client; - } - - Future droppingBeforeSend(SentryEvent event, Hint hint) async { - return null; - } - - SentryTransaction fakeTransaction() { - return SentryTransaction( - tracer, - sdk: SdkVersion(name: 'sdk1', version: '1.0.0'), - breadcrumbs: [], - ); - } - - SentryEvent fakeFeedbackEvent() { - return SentryEvent( - type: 'feedback', - contexts: Contexts(feedback: fakeFeedback()), - level: SentryLevel.info, - ); - } - - SentryFeedback fakeFeedback() { - return SentryFeedback( - message: 'fixture-message', - contactEmail: 'fixture-contactEmail', - name: 'fixture-name', - replayId: 'fixture-replayId', - url: "https://fixture-url.com", - associatedEventId: SentryId.fromId('1d49af08b6e2c437f9052b1ecfd83dca'), - ); - } - - void mockLogger( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - loggedLevel = level; - loggedException = exception; - } -} diff --git a/dart/test/sentry_client_sdk_lifecycle_test.dart b/dart/test/sentry_client_sdk_lifecycle_test.dart deleted file mode 100644 index 7d780d0cf9..0000000000 --- a/dart/test/sentry_client_sdk_lifecycle_test.dart +++ /dev/null @@ -1,195 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/platform/mock_platform.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:test/test.dart'; - -import 'mocks/mock_client_report_recorder.dart'; -import 'mocks/mock_log_batcher.dart'; -import 'mocks/mock_transport.dart'; -import 'sentry_client_test.dart'; -import 'test_utils.dart'; -import 'utils/url_details_test.dart'; - -void main() { - group('SDK lifecycle callbacks', () { - late Fixture fixture; - - setUp(() => fixture = Fixture()); - - group('Logs', () { - SentryLog givenLog() { - return SentryLog( - timestamp: DateTime.now(), - traceId: SentryId.newId(), - level: SentryLogLevel.info, - body: 'test', - attributes: { - 'attribute': SentryLogAttribute.string('value'), - }, - ); - } - - test('captureLog triggers OnBeforeCaptureLog', () async { - fixture.options.enableLogs = true; - fixture.options.environment = 'test-environment'; - fixture.options.release = 'test-release'; - - final log = givenLog(); - - final scope = Scope(fixture.options); - final span = MockSpan(); - scope.span = span; - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - client.lifeCycleRegistry.registerCallback((event) { - event.log.attributes['test'] = - SentryLogAttribute.string('test-value'); - }); - - await client.captureLog(log, scope: scope); - - final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; - expect(mockLogBatcher.addLogCalls.length, 1); - final capturedLog = mockLogBatcher.addLogCalls.first; - - expect(capturedLog.attributes['test']?.value, "test-value"); - expect(capturedLog.attributes['test']?.type, 'string'); - }); - }); - - group('SentryEvent', () { - test('captureEvent triggers OnBeforeSendEvent', () async { - fixture.options.enableLogs = true; - fixture.options.environment = 'test-environment'; - fixture.options.release = 'test-release'; - - final event = SentryEvent(); - - final scope = Scope(fixture.options); - final span = MockSpan(); - scope.span = span; - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - client.lifeCycleRegistry.registerCallback((event) { - event.event.release = '999'; - }); - - await client.captureEvent(event, scope: scope); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.release, '999'); - }); - }); - }); -} - -class Fixture { - final recorder = MockClientReportRecorder(); - final transport = MockTransport(); - - final options = defaultTestOptions() - ..platform = MockPlatform.iOS() - ..groupExceptions = true; - - late SentryTransactionContext _context; - late SentryTracer tracer; - - SentryLevel? loggedLevel; - Object? loggedException; - - SentryClient getSut({ - bool sendDefaultPii = false, - bool attachStacktrace = true, - bool attachThreads = false, - double? sampleRate, - BeforeSendCallback? beforeSend, - BeforeSendTransactionCallback? beforeSendTransaction, - BeforeSendCallback? beforeSendFeedback, - EventProcessor? eventProcessor, - bool provideMockRecorder = true, - bool debug = false, - Transport? transport, - }) { - options.tracesSampleRate = 1.0; - options.sendDefaultPii = sendDefaultPii; - options.attachStacktrace = attachStacktrace; - options.attachThreads = attachThreads; - options.sampleRate = sampleRate; - options.beforeSend = beforeSend; - options.beforeSendTransaction = beforeSendTransaction; - options.beforeSendFeedback = beforeSendFeedback; - options.debug = debug; - options.log = mockLogger; - - if (eventProcessor != null) { - options.addEventProcessor(eventProcessor); - } - - // Internally also creates a SentryClient instance - final hub = Hub(options); - _context = SentryTransactionContext( - 'name', - 'op', - ); - tracer = SentryTracer(_context, hub); - - // Reset transport - options.transport = transport ?? this.transport; - - // Again create SentryClient instance - final client = SentryClient(options); - - if (provideMockRecorder) { - options.recorder = recorder; - } - return client; - } - - Future droppingBeforeSend(SentryEvent event, Hint hint) async { - return null; - } - - SentryTransaction fakeTransaction() { - return SentryTransaction( - tracer, - sdk: SdkVersion(name: 'sdk1', version: '1.0.0'), - breadcrumbs: [], - ); - } - - SentryEvent fakeFeedbackEvent() { - return SentryEvent( - type: 'feedback', - contexts: Contexts(feedback: fakeFeedback()), - level: SentryLevel.info, - ); - } - - SentryFeedback fakeFeedback() { - return SentryFeedback( - message: 'fixture-message', - contactEmail: 'fixture-contactEmail', - name: 'fixture-name', - replayId: 'fixture-replayId', - url: "https://fixture-url.com", - associatedEventId: SentryId.fromId('1d49af08b6e2c437f9052b1ecfd83dca'), - ); - } - - void mockLogger( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - loggedLevel = level; - loggedException = exception; - } -} diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart deleted file mode 100644 index 33eb43697c..0000000000 --- a/dart/test/sentry_client_test.dart +++ /dev/null @@ -1,2833 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/client_reports/discard_reason.dart'; -import 'package:sentry/src/client_reports/noop_client_report_recorder.dart'; -import 'package:sentry/src/event_processor/exception/exception_group_event_processor.dart'; -import 'package:sentry/src/platform/mock_platform.dart'; -import 'package:sentry/src/sentry_item_type.dart'; -import 'package:sentry/src/sentry_stack_trace_factory.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:sentry/src/transport/client_report_transport.dart'; -import 'package:sentry/src/transport/data_category.dart'; -import 'package:sentry/src/transport/noop_transport.dart'; -import 'package:sentry/src/transport/spotlight_http_transport.dart'; -import 'package:sentry/src/utils/iterable_utils.dart'; -import 'package:test/test.dart'; -import 'package:sentry/src/noop_log_batcher.dart'; - -import 'mocks.dart'; -import 'mocks/mock_client_report_recorder.dart'; -import 'mocks/mock_hub.dart'; -import 'mocks/mock_transport.dart'; -import 'test_utils.dart'; -import 'utils/url_details_test.dart'; -import 'mocks/mock_log_batcher.dart'; - -void main() { - group('SentryClient captures message', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('should capture event stacktrace', () async { - final client = fixture.getSut(attachStacktrace: false); - final event = SentryEvent(); - await client.captureEvent( - event, - stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', - ); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.threads?.first.stacktrace, isA()); - }); - - test('should attach event stacktrace', () async { - final client = fixture.getSut(); - final event = SentryEvent(); - await client.captureEvent(event); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.threads?.first.stacktrace, isA()); - }); - - test('should not attach event stacktrace', () async { - final client = fixture.getSut(attachStacktrace: false); - final event = SentryEvent(); - await client.captureEvent(event); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.threads?.first.stacktrace, isNull); - }); - - test('should not attach event stacktrace if event has throwable', () async { - final client = fixture.getSut(); - - SentryEvent event; - try { - throw StateError('Error'); - } on Error catch (err) { - event = SentryEvent(throwable: err); - } - - await client.captureEvent( - event, - stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', - ); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.threads?.first.stacktrace, isNull); - expect(capturedEvent.exceptions?.first.stackTrace, isNotNull); - }); - - test('should not attach event stacktrace if event has exception', () async { - final client = fixture.getSut(); - - final exception = SentryException( - type: 'Exception', - value: 'an exception', - stackTrace: SentryStackTraceFactory(fixture.options) - .parse('#0 baz (file:///pathto/test.dart:50:3)'), - ); - final event = SentryEvent(exceptions: [exception]); - - await client.captureEvent( - event, - stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', - ); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.threads?.first.stacktrace, isNull); - expect(capturedEvent.exceptions?.first.stackTrace, isNotNull); - }); - - test( - 'should attach isolate info in thread', - () async { - final client = fixture.getSut(attachThreads: true); - - await client.captureException( - Exception(), - stackTrace: StackTrace.current, - ); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.threads?.first.current, true); - expect(capturedEvent.threads?.first.crashed, true); - expect(capturedEvent.threads?.first.name, isNotNull); - expect(capturedEvent.threads?.first.id, isNotNull); - - expect( - capturedEvent.exceptions?.first.threadId, - capturedEvent.threads?.first.id, - ); - }, - onPlatform: { - 'js': Skip("Isolates don't exist on the web"), - 'wasm': Skip("Isolates don't exist on the web") - }, - ); - - test( - 'should not attach isolate info in thread if disabled', - () async { - final client = fixture.getSut(attachThreads: false); - - await client.captureException( - Exception(), - stackTrace: StackTrace.current, - ); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.threads, null); - }, - onPlatform: {'js': Skip("Isolates don't exist on the web")}, - ); - - test('should capture message', () async { - final client = fixture.getSut(); - await client.captureMessage( - 'simple message 1', - template: 'simple message %d', - params: [1], - level: SentryLevel.error, - ); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.message!.formatted, 'simple message 1'); - expect(capturedEvent.message!.template, 'simple message %d'); - expect(capturedEvent.message!.params, [1]); - expect(capturedEvent.level, SentryLevel.error); - }); - - test('capture message defaults to info level', () async { - final client = fixture.getSut(); - await client.captureMessage( - 'simple message 1', - ); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.level, SentryLevel.info); - }); - - test('should capture message without stacktrace', () async { - final client = fixture.getSut(attachStacktrace: false); - await client.captureMessage('message', level: SentryLevel.error); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.threads?.first.stacktrace, isNull); - }); - - test('event envelope contains dsn', () async { - final client = fixture.getSut(); - final event = SentryEvent(); - await client.captureEvent(event); - - final capturedEnvelope = (fixture.transport).envelopes.first; - - expect(capturedEnvelope.header.dsn, fixture.options.dsn); - }); - }); - - group('SentryClient captures exception', () { - Error error; - StackTrace stackTrace; - - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('should capture error', () async { - try { - throw StateError('Error'); - } on Error catch (err, stack) { - error = err; - stackTrace = stack; - } - - final client = fixture.getSut(); - await client.captureException(error, stackTrace: stackTrace); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.exceptions?.first is SentryException, true); - expect(capturedEvent.exceptions?.first.stackTrace, isNotNull); - }); - }); - - group('SentryClient captures exception cause', () { - dynamic exception; - - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('should capture exception cause', () async { - fixture.options.addExceptionCauseExtractor( - ExceptionWithCauseExtractor(), - ); - - final cause = Object(); - exception = ExceptionWithCause(cause, null); - - final client = fixture.getSut( - eventProcessor: ExceptionGroupEventProcessor(fixture.options), - ); - await client.captureException(exception, stackTrace: null); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.exceptions?.length, 2); - - final firstException = capturedEvent.exceptions?[0]; - expect(firstException is SentryException, true); - expect(firstException?.mechanism?.source, "cause"); - expect(firstException?.mechanism?.parentId, 0); - expect(firstException?.mechanism?.exceptionId, 1); - expect(firstException?.mechanism?.isExceptionGroup, isNull); - - final secondException = capturedEvent.exceptions?[1]; - expect(secondException is SentryException, true); - expect(secondException?.mechanism?.source, null); - expect(secondException?.mechanism?.parentId, null); - expect(secondException?.mechanism?.exceptionId, 0); - expect(secondException?.mechanism?.isExceptionGroup, isTrue); - }); - - test('should capture cause stacktrace', () async { - fixture.options.addExceptionCauseExtractor( - ExceptionWithCauseExtractor(), - ); - - final cause = Object(); - final stackTrace = ''' -#0 baz (file:///pathto/test.dart:50:3) - -#1 bar (file:///pathto/test.dart:46:9) - '''; - - exception = ExceptionWithCause(cause, stackTrace); - - final client = fixture.getSut( - attachStacktrace: true, - eventProcessor: ExceptionGroupEventProcessor(fixture.options), - ); - await client.captureException(exception, stackTrace: null); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.exceptions?[0].stackTrace, isNotNull); - expect(capturedEvent.exceptions?[0].stackTrace!.frames.first.fileName, - 'test.dart'); - expect(capturedEvent.exceptions?[0].stackTrace!.frames.first.lineNo, 46); - expect(capturedEvent.exceptions?[0].stackTrace!.frames.first.colNo, 9); - }); - - test('should capture custom stacktrace', () async { - fixture.options.addExceptionStackTraceExtractor( - ExceptionWithStackTraceExtractor(), - ); - - final stackTrace = StackTrace.fromString(''' -#0 baz (file:///pathto/test.dart:50:3) - -#1 bar (file:///pathto/test.dart:46:9) - '''); - - exception = ExceptionWithStackTrace(stackTrace); - - final client = fixture.getSut( - attachStacktrace: true, - eventProcessor: ExceptionGroupEventProcessor(fixture.options), - ); - await client.captureException(exception, stackTrace: null); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.exceptions?[0].stackTrace, isNotNull); - expect(capturedEvent.exceptions?[0].stackTrace!.frames.first.fileName, - 'test.dart'); - expect(capturedEvent.exceptions?[0].stackTrace!.frames.first.lineNo, 46); - expect(capturedEvent.exceptions?[0].stackTrace!.frames.first.colNo, 9); - }); - - test('should not capture cause stacktrace when attachStacktrace is false', - () async { - fixture.options.addExceptionCauseExtractor( - ExceptionWithCauseExtractor(), - ); - - final cause = Object(); - exception = ExceptionWithCause(cause, null); - - final client = fixture.getSut( - attachStacktrace: false, - eventProcessor: ExceptionGroupEventProcessor(fixture.options), - ); - await client.captureException(exception, stackTrace: null); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.exceptions?[0].stackTrace, isNull); - }); - - test( - 'should not capture cause stacktrace when attachStacktrace is false and StackTrace.empty', - () async { - fixture.options.addExceptionCauseExtractor( - ExceptionWithCauseExtractor(), - ); - - final cause = Object(); - exception = ExceptionWithCause(cause, StackTrace.empty); - - final client = fixture.getSut( - attachStacktrace: false, - eventProcessor: ExceptionGroupEventProcessor(fixture.options), - ); - await client.captureException(exception, stackTrace: null); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.exceptions?[0].stackTrace, isNull); - }); - - test('should capture cause exception with Stackframe.current', () async { - fixture.options.addExceptionCauseExtractor( - ExceptionWithCauseExtractor(), - ); - - final cause = Object(); - exception = ExceptionWithCause(cause, null); - - final client = fixture.getSut( - attachStacktrace: true, - eventProcessor: ExceptionGroupEventProcessor(fixture.options), - ); - await client.captureException(exception, stackTrace: null); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.exceptions?[0].stackTrace, isNotNull); - }); - - test('should capture sentry frames exception', () async { - fixture.options.addExceptionCauseExtractor( - ExceptionWithCauseExtractor(), - ); - - final cause = Object(); - final stackTrace = ''' -#0 init (package:sentry/sentry.dart:46:9) -#1 bar (file:///pathto/test.dart:46:9) - -#2 capture (package:sentry/sentry.dart:46:9) - '''; - exception = ExceptionWithCause(cause, stackTrace); - - final client = fixture.getSut( - attachStacktrace: true, - eventProcessor: ExceptionGroupEventProcessor(fixture.options), - ); - await client.captureException(exception, stackTrace: null); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - final sentryFramesCount = capturedEvent.exceptions?[0].stackTrace!.frames - .where((frame) => frame.package == 'sentry') - .length; - - expect(sentryFramesCount, 2); - }); - }); - - group('SentryClient captures exception and stacktrace', () { - late Fixture fixture; - - Error error; - - dynamic exception; - - final stacktrace = ''' -#0 baz (file:///pathto/test.dart:50:3) - -#1 bar (file:///pathto/test.dart:46:9) - '''; - - setUp(() { - fixture = Fixture(); - }); - - test('should capture error', () async { - try { - throw StateError('Error'); - } on Error catch (err) { - error = err; - } - - final client = fixture.getSut(); - await client.captureException(error, stackTrace: stacktrace); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.exceptions?.first is SentryException, true); - expect(capturedEvent.exceptions?.first.stackTrace, isNotNull); - expect(capturedEvent.exceptions?.first.stackTrace!.frames.first.fileName, - 'test.dart'); - expect( - capturedEvent.exceptions?.first.stackTrace!.frames.first.lineNo, 46); - expect(capturedEvent.exceptions?.first.stackTrace!.frames.first.colNo, 9); - }); - - test('should capture exception', () async { - try { - throw Exception('Error'); - } catch (err) { - exception = err; - } - - final client = fixture.getSut(); - await client.captureException(exception, stackTrace: stacktrace); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.exceptions?.first is SentryException, true); - expect(capturedEvent.exceptions?.first.stackTrace!.frames.first.fileName, - 'test.dart'); - expect( - capturedEvent.exceptions?.first.stackTrace!.frames.first.lineNo, 46); - expect(capturedEvent.exceptions?.first.stackTrace!.frames.first.colNo, 9); - }); - - test('should capture exception with Stackframe.current', () async { - try { - throw Exception('Error'); - } catch (err) { - exception = err; - } - - final client = fixture.getSut(); - await client.captureException(exception); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.exceptions?.first.stackTrace, isNotNull); - }); - - test('should capture exception without Stackframe.current', () async { - try { - throw Exception('Error'); - } catch (err) { - exception = err; - } - - final client = fixture.getSut(attachStacktrace: false); - await client.captureException(exception); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.exceptions?.first.stackTrace, isNull); - }); - - test('should capture sentry frames exception', () async { - try { - throw Exception('Error'); - } catch (err) { - exception = err; - } - - final stackTrace = ''' -#0 baz (file:///pathto/test.dart:50:3) - -#1 bar (file:///pathto/test.dart:46:9) -#2 capture (package:sentry/sentry.dart:46:9) - '''; - - final client = fixture.getSut(); - await client.captureException(exception, stackTrace: stackTrace); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect( - capturedEvent.exceptions?.first.stackTrace!.frames - .any((frame) => frame.package == 'sentry'), - true, - ); - }); - - test('should remove sentry frames if null stackStrace', () async { - final throwable = Object(); - - final client = fixture.getSut(attachStacktrace: true); - await client.captureException(throwable, stackTrace: null); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - final sentryFramesCount = capturedEvent.exceptions?[0].stackTrace!.frames - .where((frame) => frame.package == 'sentry') - .length; - - expect(sentryFramesCount, 0); - }); - - test('should remove sentry frames if empty stackStrace', () async { - final throwable = Object(); - - final client = fixture.getSut(attachStacktrace: true); - await client.captureException(throwable, stackTrace: StackTrace.empty); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - final sentryFramesCount = capturedEvent.exceptions?[0].stackTrace!.frames - .where((frame) => frame.package == 'sentry') - .length; - - expect(sentryFramesCount, 0); - }); - }); - - group('SentryClient captures transaction', () { - late Fixture fixture; - - Error error; - - setUp(() { - fixture = Fixture(); - }); - - test( - 'when scope does not have an active transaction, trace state is set on the envelope from scope', - () async { - final client = fixture.getSut(); - final scope = Scope(fixture.options); - await client.captureEvent(SentryEvent(), scope: scope); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedTraceContext = capturedEnvelope.header.traceContext; - final capturedTraceId = capturedTraceContext?.traceId; - final propagationContextTraceId = scope.propagationContext.traceId; - - expect(capturedTraceContext, isNotNull); - expect(capturedTraceId, propagationContextTraceId); - }); - - test('attaches trace context from span if none present yet', () async { - final client = fixture.getSut(); - final spanContext = SentrySpanContext( - traceId: SentryId.newId(), - spanId: SpanId.newId(), - operation: 'op.load', - ); - final scope = Scope(fixture.options); - scope.span = SentrySpan(fixture.tracer, spanContext, MockHub()); - - final sentryEvent = SentryEvent(); - await client.captureEvent(sentryEvent, scope: scope); - - expect(fixture.transport.envelopes.length, 1); - expect(spanContext.spanId, sentryEvent.contexts.trace!.spanId); - expect(spanContext.traceId, sentryEvent.contexts.trace!.traceId); - }); - - test( - 'attaches trace context from scope if none present yet and no span on scope', - () async { - final client = fixture.getSut(); - - final scope = Scope(fixture.options); - final scopePropagationContext = scope.propagationContext; - - final sentryEvent = SentryEvent(); - await client.captureEvent(sentryEvent, scope: scope); - - expect(fixture.transport.envelopes.length, 1); - expect( - scopePropagationContext.traceId, sentryEvent.contexts.trace!.traceId); - // not checking for span id as it should be a new generated random span id - }); - - test('keeps existing trace context if already present', () async { - final client = fixture.getSut(); - - final spanContext = SentrySpanContext( - traceId: SentryId.newId(), - spanId: SpanId.newId(), - operation: 'op.load', - ); - final scope = Scope(fixture.options); - scope.span = SentrySpan(fixture.tracer, spanContext, MockHub()); - - final propagationContext = scope.propagationContext; - final preExistingSpanContext = SentryTraceContext( - traceId: SentryId.newId(), - spanId: SpanId.newId(), - operation: 'op.load'); - - final sentryEvent = SentryEvent(); - sentryEvent.contexts.trace = preExistingSpanContext; - await client.captureEvent(sentryEvent, scope: scope); - - expect(fixture.transport.envelopes.length, 1); - expect( - preExistingSpanContext.traceId, sentryEvent.contexts.trace!.traceId); - expect(preExistingSpanContext.spanId, sentryEvent.contexts.trace!.spanId); - expect(spanContext.traceId, isNot(sentryEvent.contexts.trace!.traceId)); - expect(spanContext.spanId, isNot(sentryEvent.contexts.trace!.spanId)); - expect(propagationContext.traceId, - isNot(sentryEvent.contexts.trace!.traceId)); - }); - - test( - 'uses propagation context on scope for trace header if no transaction is on scope', - () async { - final client = fixture.getSut(); - - final scope = Scope(fixture.options); - final scopePropagationContext = scope.propagationContext; - - final sentryEvent = SentryEvent(); - await client.captureEvent(sentryEvent, scope: scope); - - final capturedEnvelope = fixture.transport.envelopes.first; - final capturedTraceContext = capturedEnvelope.header.traceContext; - - expect(fixture.transport.envelopes.length, 1); - expect(scope.span, isNull); - expect(capturedTraceContext, isNotNull); - expect(scopePropagationContext.traceId, capturedTraceContext!.traceId); - }); - - test( - 'uses trace context on transaction for trace header if a transaction is on scope', - () async { - final client = fixture.getSut(); - - final spanContext = SentrySpanContext( - traceId: SentryId.newId(), - spanId: SpanId.newId(), - operation: 'op.load', - ); - final scope = Scope(fixture.options); - scope.span = SentrySpan(fixture.tracer, spanContext, MockHub()); - - final sentryEvent = SentryEvent(); - await client.captureEvent(sentryEvent, scope: scope); - - final capturedEnvelope = fixture.transport.envelopes.first; - final capturedTraceContext = capturedEnvelope.header.traceContext; - - expect(fixture.transport.envelopes.length, 1); - expect(scope.span, isNotNull); - expect(capturedTraceContext, isNotNull); - expect( - scope.span!.traceContext()!.traceId, capturedTraceContext!.traceId); - }); - - test('should contain a transaction in the envelope', () async { - try { - throw StateError('Error'); - } on Error catch (err) { - error = err; - } - - final client = fixture.getSut(); - final tr = SentryTransaction(fixture.tracer, throwable: error); - await client.captureTransaction(tr); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedTr = await transactionFromEnvelope(capturedEnvelope); - - expect(capturedTr['type'], 'transaction'); - }); - - test('should not set exception to transactions', () async { - try { - throw StateError('Error'); - } on Error catch (err) { - error = err; - } - - final client = fixture.getSut(); - final tr = SentryTransaction(fixture.tracer, throwable: error); - await client.captureTransaction(tr); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await transactionFromEnvelope(capturedEnvelope); - - expect(capturedEvent['exception'], isNull); - }); - - test('attachments not added to captured transaction per default', () async { - final attachment = SentryAttachment.fromUint8List( - Uint8List.fromList([0, 0, 0, 0]), - 'test.txt', - ); - final scope = Scope(fixture.options); - scope.addAttachment(attachment); - - final client = fixture.getSut(); - final tr = SentryTransaction(fixture.tracer); - await client.captureTransaction(tr, scope: scope); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedAttachments = capturedEnvelope.items - .where((item) => item.header.type == SentryItemType.attachment); - - expect(capturedAttachments.isEmpty, true); - }); - - test('attachments added to captured event', () async { - final attachment = SentryAttachment.fromUint8List( - Uint8List.fromList([0, 0, 0, 0]), - 'test.txt', - addToTransactions: true, - ); - final scope = Scope(fixture.options); - scope.addAttachment(attachment); - - final client = fixture.getSut(); - final tr = SentryTransaction(fixture.tracer); - await client.captureTransaction(tr, scope: scope); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedAttachments = capturedEnvelope.items - .where((item) => item.header.type == SentryItemType.attachment); - - expect(capturedAttachments.isNotEmpty, true); - }); - - test('attachments added to captured event per default', () async { - final attachment = SentryAttachment.fromUint8List( - Uint8List.fromList([0, 0, 0, 0]), - 'test.txt', - ); - final scope = Scope(fixture.options); - scope.addAttachment(attachment); - - final client = fixture.getSut(); - final event = SentryEvent(); - await client.captureEvent(event, scope: scope); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedAttachments = capturedEnvelope.items - .where((item) => item.header.type == SentryItemType.attachment); - - expect(capturedAttachments.isNotEmpty, true); - }); - - test('should return empty for when transaction is discarded', () async { - final client = fixture.getSut(eventProcessor: DropAllEventProcessor()); - final tr = SentryTransaction(fixture.tracer); - final id = await client.captureTransaction(tr); - - expect(id, SentryId.empty()); - }); - - test('transaction envelope contains dsn', () async { - final client = fixture.getSut(); - final tr = SentryTransaction(fixture.tracer); - await client.captureTransaction(tr); - - final capturedEnvelope = (fixture.transport).envelopes.first; - - expect(capturedEnvelope.header.dsn, fixture.options.dsn); - }); - }); - - group('SentryClient : apply scope to the captured event', () { - late Scope scope; - - final level = SentryLevel.error; - const transaction = '/test/scope'; - const fingerprint = ['foo', 'bar', 'baz']; - final user = SentryUser(id: '123', username: 'test'); - final crumb = Breadcrumb(message: 'bread'); - const scopeTagKey = 'scope-tag'; - const scopeTagValue = 'scope-tag-value'; - const eventTagKey = 'event-tag'; - const eventTagValue = 'event-tag-value'; - const scopeExtraKey = 'scope-extra'; - const scopeExtraValue = 'scope-extra-value'; - const eventExtraKey = 'event-extra'; - const eventExtraValue = 'event-extra-value'; - - final event = SentryEvent( - tags: const {eventTagKey: eventTagValue}, - // ignore: deprecated_member_use_from_same_package - extra: const {eventExtraKey: eventExtraValue}, - modules: const {eventExtraKey: eventExtraValue}, - level: SentryLevel.warning, - ); - - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - - scope = Scope(fixture.options) - ..level = level - ..transaction = transaction - ..fingerprint = fingerprint - ..addBreadcrumb(crumb) - ..setTag(scopeTagKey, scopeTagValue) - // ignore: deprecated_member_use_from_same_package - ..setExtra(scopeExtraKey, scopeExtraValue) - ..replayId = SentryId.fromId('1'); - - scope.setUser(user); - }); - - test('should apply the scope to event', () async { - final client = fixture.getSut(); - await client.captureEvent(event, scope: scope); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.user?.id, user.id); - expect(capturedEvent.level!.name, SentryLevel.error.name); - expect(capturedEvent.transaction, transaction); - expect(capturedEvent.fingerprint, fingerprint); - expect(capturedEvent.breadcrumbs?.first.toJson(), crumb.toJson()); - expect(capturedEvent.tags, { - scopeTagKey: scopeTagValue, - eventTagKey: eventTagValue, - }); - // ignore: deprecated_member_use_from_same_package - expect(capturedEvent.extra, { - scopeExtraKey: scopeExtraValue, - eventExtraKey: eventExtraValue, - }); - expect( - capturedEnvelope.header.traceContext?.replayId, SentryId.fromId('1')); - }); - - test('should apply the scope to feedback event', () async { - final client = fixture.getSut(); - final feedback = fixture.fakeFeedback(); - await client.captureFeedback(feedback, scope: scope); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.user?.id, user.id); - expect(capturedEvent.level!.name, SentryLevel.error.name); - expect(capturedEvent.transaction, transaction); - expect(capturedEvent.fingerprint, fingerprint); - expect(capturedEvent.breadcrumbs, isNull); - expect(capturedEvent.tags, { - scopeTagKey: scopeTagValue, - }); - // ignore: deprecated_member_use_from_same_package - expect(capturedEvent.extra, isNull); - }); - }); - - group('SentryClient : apply partial scope to the captured event', () { - late Fixture fixture; - - late String transaction; - late String eventTransaction; - late List fingerprint; - late List eventFingerprint; - late SentryUser user; - late Breadcrumb crumb; - late SentryUser eventUser; - late List eventCrumbs; - late SentryEvent event; - - Future createScope(SentryOptions options) async { - final scope = Scope(options) - ..transaction = transaction - ..fingerprint = fingerprint; - await scope.addBreadcrumb(crumb); - await scope.setUser(user); - return scope; - } - - setUp(() { - fixture = Fixture(); - - transaction = '/test/scope'; - eventTransaction = '/event/transaction'; - fingerprint = ['foo', 'bar', 'baz']; - eventFingerprint = ['123', '456', '798']; - user = SentryUser(id: '123'); - crumb = Breadcrumb(message: 'bread'); - eventUser = SentryUser(id: '987'); - eventCrumbs = [Breadcrumb(message: 'bread')]; - event = SentryEvent( - level: SentryLevel.warning, - transaction: eventTransaction, - user: eventUser, - fingerprint: eventFingerprint, - breadcrumbs: eventCrumbs, - ); - }); - - test('should not apply the scope to non null event fields', () async { - final client = fixture.getSut(sendDefaultPii: true); - final scope = await createScope(fixture.options); - - await client.captureEvent(event, scope: scope); - - final capturedEnvelope = fixture.transport.envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.user!.id, eventUser.id); - expect(capturedEvent.level!.name, SentryLevel.warning.name); - expect(capturedEvent.transaction, eventTransaction); - expect(capturedEvent.fingerprint, eventFingerprint); - expect(capturedEvent.breadcrumbs?.map((e) => e.toJson()), - eventCrumbs.map((e) => e.toJson())); - }); - - test('should apply the scope user to null event user fields', () async { - final client = fixture.getSut(sendDefaultPii: true); - final scope = await createScope(fixture.options); - - await scope.setUser(SentryUser(id: '987')); - - event.user = SentryUser(id: '123', username: 'foo bar'); - await client.captureEvent(event, scope: scope); - - final capturedEnvelope = fixture.transport.envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.user!.id, '123'); - expect(capturedEvent.user!.username, 'foo bar'); - expect(capturedEvent.level!.name, SentryLevel.warning.name); - expect(capturedEvent.transaction, eventTransaction); - expect(capturedEvent.fingerprint, eventFingerprint); - expect(capturedEvent.breadcrumbs?.map((e) => e.toJson()), - eventCrumbs.map((e) => e.toJson())); - }); - - test('merge scope user and event user extra', () async { - final client = fixture.getSut(sendDefaultPii: true); - final scope = await createScope(fixture.options); - - await scope.setUser( - SentryUser( - id: 'id', - data: { - 'foo': 'bar', - 'bar': 'foo', - }, - ), - ); - - event.user = SentryUser( - id: 'id', - data: { - 'foo': 'this bar is more important', - 'event': 'Really important event' - }, - ); - await client.captureEvent(event, scope: scope); - - final capturedEnvelope = fixture.transport.envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.user?.data?['foo'], 'this bar is more important'); - expect(capturedEvent.user?.data?['bar'], 'foo'); - expect(capturedEvent.user?.data?['event'], 'Really important event'); - }); - }); - - group('SentryClient: user & user ip', () { - late Fixture fixture; - late SentryUser fakeUser; - - setUp(() { - fixture = Fixture(); - fakeUser = getFakeUser(); - }); - - test('event has no user and sendDefaultPii = true', () async { - final client = fixture.getSut(sendDefaultPii: true); - final fakeEvent = SentryEvent(); - expect(fakeEvent.user, isNull); - - await client.captureEvent(fakeEvent); - - final capturedEnvelope = fixture.transport.envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(fixture.transport.envelopes.length, 1); - expect(capturedEvent.user, isNotNull); - expect(capturedEvent.user?.ipAddress, defaultIpAddress); - }); - - test('event has no user and sendDefaultPii = false', () async { - final client = fixture.getSut(sendDefaultPii: false); - var fakeEvent = SentryEvent(); - expect(fakeEvent.user, isNull); - - await client.captureEvent(fakeEvent); - - final capturedEnvelope = fixture.transport.envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(fixture.transport.envelopes.length, 1); - expect(capturedEvent.user, isNull); - }); - - test('event has a user with IP address', () async { - final client = fixture.getSut(sendDefaultPii: true); - final fakeEvent = getFakeEvent(); - - expect(fakeEvent.user?.ipAddress, isNotNull); - await client.captureEvent(fakeEvent); - - final capturedEnvelope = fixture.transport.envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(fixture.transport.envelopes.length, 1); - expect(capturedEvent.user, isNotNull); - // fakeEvent has a user which is not null - expect(capturedEvent.user?.ipAddress, fakeEvent.user!.ipAddress); - expect(capturedEvent.user?.id, fakeEvent.user!.id); - expect(capturedEvent.user?.email, fakeEvent.user!.email); - }); - - test('event has a user without IP address and sendDefaultPii = true', - () async { - final client = fixture.getSut(sendDefaultPii: true); - final fakeEvent = getFakeEvent(); - fakeEvent.user = fakeUser; - - expect(fakeEvent.user?.ipAddress, isNull); - - await client.captureEvent(fakeEvent); - - final capturedEnvelope = fixture.transport.envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(fixture.transport.envelopes.length, 1); - expect(capturedEvent.user, isNotNull); - expect(capturedEvent.user?.ipAddress, defaultIpAddress); - expect(capturedEvent.user?.id, fakeUser.id); - expect(capturedEvent.user?.email, fakeUser.email); - }); - - test('event has a user without IP address and sendDefaultPii = false', - () async { - final client = fixture.getSut(sendDefaultPii: false); - final fakeEvent = getFakeEvent(); - fakeEvent.user = fakeUser; - - expect(fakeEvent.user?.ipAddress, isNull); - - await client.captureEvent(fakeEvent); - - final capturedEnvelope = fixture.transport.envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(fixture.transport.envelopes.length, 1); - expect(capturedEvent.user, isNotNull); - expect(capturedEvent.user?.ipAddress, isNull); - expect(capturedEvent.user?.id, fakeUser.id); - expect(capturedEvent.user?.email, fakeUser.email); - }); - }); - - group('SentryClient sampling', () { - late Fixture fixture; - late SentryEvent fakeEvent; - - setUp(() { - fixture = Fixture(); - fakeEvent = getFakeEvent(); - }); - - test('captures event, sample rate is 100% enabled', () async { - final client = fixture.getSut(sampleRate: 1.0); - await client.captureEvent(fakeEvent); - - expect(fixture.transport.called(1), true); - }); - - test('do not capture event, sample rate is 0% disabled', () async { - final client = fixture.getSut(sampleRate: 0.0); - await client.captureEvent(fakeEvent); - - expect(fixture.transport.called(0), true); - }); - - test('captures event, sample rate is null, disabled', () async { - final client = fixture.getSut(); - await client.captureEvent(fakeEvent); - - expect(fixture.transport.called(1), true); - }); - - test('capture feedback event, sample rate is 0% disabled', () async { - final client = fixture.getSut(sampleRate: 0.0); - - final fakeFeedback = fixture.fakeFeedback(); - await client.captureFeedback(fakeFeedback); - - expect(fixture.transport.called(1), true); - }); - }); - - group('SentryClient ignored errors', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - fixture.options.ignoreErrors = ["my-error", "^error-.*\$"]; - }); - - test('drop event if error message fully matches ignoreErrors value', - () async { - final event = SentryEvent(message: SentryMessage("my-error")); - - final client = fixture.getSut(); - await client.captureEvent(event); - - expect(fixture.transport.called(0), true); - }); - - test('drop event if error message partially matches ignoreErrors value', - () async { - final event = SentryEvent(message: SentryMessage("this is my-error-foo")); - - final client = fixture.getSut(); - await client.captureEvent(event); - - expect(fixture.transport.called(0), true); - }); - - test( - 'drop event if error message partially matches ignoreErrors regex value', - () async { - final event = SentryEvent(message: SentryMessage("error-test message")); - - final client = fixture.getSut(); - await client.captureEvent(event); - - expect(fixture.transport.called(0), true); - }); - - test('send event if error message does not match ignoreErrors value', - () async { - final event = SentryEvent(message: SentryMessage("warning")); - - final client = fixture.getSut(); - await client.captureEvent(event); - - expect(fixture.transport.called(1), true); - }); - - test('send event if no values are set for ignoreErrors', () async { - fixture.options.ignoreErrors = []; - final event = SentryEvent(message: SentryMessage("this is a test event")); - - final client = fixture.getSut(); - await client.captureEvent(event); - - expect(fixture.transport.called(1), true); - }); - }); - - group('SentryClient ignored transactions', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - fixture.options.ignoreTransactions = [ - "my-transaction", - "^transaction-.*\$" - ]; - }); - - test('drop transaction if name fully matches ignoreTransaction value', - () async { - final client = fixture.getSut(); - final fakeTransaction = fixture.fakeTransaction(); - fakeTransaction.tracer.name = "my-transaction"; - await client.captureTransaction(fakeTransaction); - - expect(fixture.transport.called(0), true); - }); - - test('drop transaction if name partially matches ignoreTransaction value', - () async { - final client = fixture.getSut(); - final fakeTransaction = fixture.fakeTransaction(); - fakeTransaction.tracer.name = "this is a my-transaction-test"; - await client.captureTransaction(fakeTransaction); - - expect(fixture.transport.called(0), true); - }); - - test( - 'drop transaction if name partially matches ignoreTransaction regex value', - () async { - final client = fixture.getSut(); - final fakeTransaction = fixture.fakeTransaction(); - fakeTransaction.tracer.name = "transaction-test message"; - await client.captureTransaction(fakeTransaction); - - expect(fixture.transport.called(0), true); - }); - - test('send transaction if name does not match ignoreTransaction value', - () async { - final client = fixture.getSut(); - final fakeTransaction = fixture.fakeTransaction(); - fakeTransaction.tracer.name = "capture"; - await client.captureTransaction(fakeTransaction); - - expect(fixture.transport.called(1), true); - }); - - test('send transaction if no values are set for ignoreTransaction', - () async { - fixture.options.ignoreTransactions = []; - final client = fixture.getSut(); - final fakeTransaction = fixture.fakeTransaction(); - fakeTransaction.tracer.name = "this is a test transaction"; - await client.captureTransaction(fakeTransaction); - - expect(fixture.transport.called(1), true); - }); - }); - - group('SentryClient ignored exceptions', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('addExceptionFilterForType drops matching error event throwable', - () async { - fixture.options.addExceptionFilterForType(ExceptionWithCause); - - final throwable = ExceptionWithCause(Error(), StackTrace.current); - final event = SentryEvent(throwable: throwable); - - final client = fixture.getSut(); - await client.captureEvent(event); - - expect(fixture.transport.called(0), true); - }); - - test('record ignored exceptions dropping event', () async { - fixture.options.addExceptionFilterForType(ExceptionWithCause); - - final throwable = ExceptionWithCause(Error(), StackTrace.current); - final event = SentryEvent(throwable: throwable); - - final client = fixture.getSut(); - await client.captureEvent(event); - - expect(fixture.recorder.discardedEvents.first.reason, - DiscardReason.eventProcessor); - expect( - fixture.recorder.discardedEvents.first.category, DataCategory.error); - }); - }); - - group('SentryClient before send feedback', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('before send feedback drops event', () async { - final client = fixture.getSut( - beforeSendFeedback: beforeSendFeedbackCallbackDropEvent); - final fakeFeedback = fixture.fakeFeedback(); - await client.captureFeedback(fakeFeedback); - - expect(fixture.transport.called(0), true); - }); - - test('async before send feedback drops event', () async { - final client = fixture.getSut( - beforeSendFeedback: asyncBeforeSendFeedbackCallbackDropEvent); - final fakeFeedback = fixture.fakeFeedback(); - await client.captureFeedback(fakeFeedback); - - expect(fixture.transport.called(0), true); - }); - - test( - 'before send feedback returns an feedback event and feedback event is captured', - () async { - final client = - fixture.getSut(beforeSendFeedback: beforeSendFeedbackCallback); - final fakeFeedback = fixture.fakeFeedback(); - await client.captureFeedback(fakeFeedback); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final feedbackEvent = await eventFromEnvelope(capturedEnvelope); - - expect(feedbackEvent.tags!.containsKey('theme'), true); - }); - - test('thrown error is handled', () async { - fixture.options.automatedTestMode = false; - final exception = Exception("before send exception"); - final beforeSendFeedbackCallback = (SentryEvent event, Hint hint) { - throw exception; - }; - - final client = fixture.getSut( - beforeSendFeedback: beforeSendFeedbackCallback, debug: true); - final fakeFeedback = fixture.fakeFeedback(); - await client.captureFeedback(fakeFeedback); - - expect(fixture.loggedException, exception); - expect(fixture.loggedLevel, SentryLevel.error); - }); - }); - - group('SentryClient before send transaction', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('before send transaction drops event', () async { - final client = fixture.getSut( - beforeSendTransaction: beforeSendTransactionCallbackDropEvent); - final fakeTransaction = fixture.fakeTransaction(); - await client.captureTransaction(fakeTransaction); - - expect(fixture.transport.called(0), true); - }); - - test('async before send transaction drops event', () async { - final client = fixture.getSut( - beforeSendTransaction: asyncBeforeSendTransactionCallbackDropEvent); - final fakeTransaction = fixture.fakeTransaction(); - await client.captureTransaction(fakeTransaction); - - expect(fixture.transport.called(0), true); - }); - - test( - 'before send transaction returns an transaction and transaction is captured', - () async { - final client = - fixture.getSut(beforeSendTransaction: beforeSendTransactionCallback); - final fakeTransaction = fixture.fakeTransaction(); - await client.captureTransaction(fakeTransaction); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final transaction = await transactionFromEnvelope(capturedEnvelope); - - expect(transaction['tags']!.containsKey('theme'), true); - expect(transaction['extra']!.containsKey('host'), true); - expect(transaction['sdk']!['integrations'].contains('testIntegration'), - true); - expect( - transaction['sdk']!['packages'] - .any((element) => element['name'] == 'test-pkg'), - true, - ); - expect( - transaction['breadcrumbs']! - .any((element) => element['message'] == 'processor crumb'), - true, - ); - }); - - test('thrown error is handled', () async { - fixture.options.automatedTestMode = false; - final exception = Exception("before send exception"); - final beforeSendTransactionCallback = - (SentryTransaction event, Hint hint) { - throw exception; - }; - - fixture.options.automatedTestMode = false; - final client = fixture.getSut( - beforeSendTransaction: beforeSendTransactionCallback, debug: true); - final fakeTransaction = fixture.fakeTransaction(); - await client.captureTransaction(fakeTransaction); - - expect(fixture.loggedException, exception); - expect(fixture.loggedLevel, SentryLevel.error); - }); - }); - - group('SentryClient before send', () { - late Fixture fixture; - late SentryEvent fakeEvent; - - setUp(() { - fixture = Fixture(); - fakeEvent = getFakeEvent(); - }); - - test('before send drops event', () async { - final client = fixture.getSut(beforeSend: beforeSendCallbackDropEvent); - await client.captureEvent(fakeEvent); - - expect(fixture.transport.called(0), true); - }); - - test('async before send drops event', () async { - final client = - fixture.getSut(beforeSend: asyncBeforeSendCallbackDropEvent); - await client.captureEvent(fakeEvent); - - expect(fixture.transport.called(0), true); - }); - - test('before send returns an event and event is captured', () async { - final client = fixture.getSut(beforeSend: beforeSendCallback); - await client.captureEvent(fakeEvent); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final event = await eventFromEnvelope(capturedEnvelope); - - expect(event.tags!.containsKey('theme'), true); - // ignore: deprecated_member_use_from_same_package - expect(event.extra!.containsKey('host'), true); - expect(event.modules!.containsKey('core'), true); - expect(event.sdk!.integrations.contains('testIntegration'), true); - expect( - event.sdk!.packages.any((element) => element.name == 'test-pkg'), - true, - ); - expect( - event.breadcrumbs! - .any((element) => element.message == 'processor crumb'), - true, - ); - expect(event.fingerprint!.contains('process'), true); - }); - - test('thrown error is handled', () async { - fixture.options.automatedTestMode = false; - final exception = Exception("before send exception"); - final beforeSendCallback = (SentryEvent event, Hint hint) { - throw exception; - }; - - fixture.options.automatedTestMode = false; - final client = - fixture.getSut(beforeSend: beforeSendCallback, debug: true); - - await client.captureEvent(fakeEvent); - - expect(fixture.loggedException, exception); - expect(fixture.loggedLevel, SentryLevel.error); - }); - }); - - group('EventProcessors', () { - late Fixture fixture; - late SentryEvent fakeEvent; - - setUp(() { - fixture = Fixture(); - fakeEvent = getFakeEvent(); - fixture.options.addEventProcessor(FunctionEventProcessor((event, hint) { - event.tags = {'theme': 'material'}; - // ignore: deprecated_member_use_from_same_package - event.extra?['host'] = '0.0.0.1'; - event.modules?.addAll({'core': '1.0'}); - event.breadcrumbs?.add(Breadcrumb(message: 'processor crumb')); - event.fingerprint?.add('process'); - event.sdk?.addIntegration('testIntegration'); - event.sdk?.addPackage('test-pkg', '1.0'); - - return event; - })); - }); - - test('should execute eventProcessors for event', () async { - final client = fixture.getSut(); - await client.captureEvent(fakeEvent); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final event = await eventFromEnvelope(capturedEnvelope); - - expect(event.tags!.containsKey('theme'), true); - // ignore: deprecated_member_use_from_same_package - expect(event.extra!.containsKey('host'), true); - expect(event.modules!.containsKey('core'), true); - expect(event.sdk!.integrations.contains('testIntegration'), true); - expect( - event.sdk!.packages.any((element) => element.name == 'test-pkg'), - true, - ); - expect( - event.breadcrumbs! - .any((element) => element.message == 'processor crumb'), - true, - ); - expect(event.fingerprint!.contains('process'), true); - }); - - test('should execute eventProcessors for feedback', () async { - final client = fixture.getSut(); - final fakeFeedback = fixture.fakeFeedback(); - await client.captureFeedback(fakeFeedback); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final event = await eventFromEnvelope(capturedEnvelope); - - expect(event.tags?.containsKey('theme'), true); - }); - - test('should pass hint to eventProcessors for event', () async { - final myHint = Hint(); - myHint.set('string', 'hint'); - - var executed = false; - - final client = - fixture.getSut(eventProcessor: FunctionEventProcessor((event, hint) { - expect(myHint, hint); - executed = true; - return event; - })); - - await client.captureEvent(fakeEvent, hint: myHint); - - expect(executed, true); - }); - - test('should pass hint to eventProcessors for feedback', () async { - final myHint = Hint(); - myHint.set('string', 'hint'); - - var executed = false; - - final client = - fixture.getSut(eventProcessor: FunctionEventProcessor((event, hint) { - expect(myHint, hint); - executed = true; - return event; - })); - - final fakeFeedback = fixture.fakeFeedback(); - await client.captureFeedback(fakeFeedback, hint: myHint); - - expect(executed, true); - }); - - test('should create hint when none was provided for event', () async { - var executed = false; - - final client = - fixture.getSut(eventProcessor: FunctionEventProcessor((event, hint) { - expect(hint, isNotNull); - executed = true; - return event; - })); - - await client.captureEvent(fakeEvent); - - expect(executed, true); - }); - - test('should create hint when none was provided for feedback event', - () async { - var executed = false; - - final client = - fixture.getSut(eventProcessor: FunctionEventProcessor((event, hint) { - expect(hint, isNotNull); - executed = true; - return event; - })); - - final fakeFeedback = fixture.fakeFeedback(); - await client.captureFeedback(fakeFeedback); - - expect(executed, true); - }); - - test('event processor drops the event', () async { - final client = fixture.getSut(eventProcessor: DropAllEventProcessor()); - - await client.captureEvent(fakeEvent); - - expect(fixture.transport.called(0), true); - }); - - test('event processor drops the feedback event', () async { - final client = fixture.getSut(eventProcessor: DropAllEventProcessor()); - - final fakeFeedback = fixture.fakeFeedback(); - await client.captureFeedback(fakeFeedback); - - expect(fixture.transport.called(0), true); - }); - }); - - group('SentryClient captures feedback', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('should capture feedback as event', () async { - final client = fixture.getSut(); - - final feedback = fixture.fakeFeedback(); - await client.captureFeedback(feedback); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final envelopeItem = capturedEnvelope.items.first; - final envelopeEvent = envelopeItem.originalObject as SentryEvent?; - - expect(envelopeItem, isNotNull); - expect(envelopeEvent, isNotNull); - - expect(envelopeItem.header.type, 'feedback'); - - expect(envelopeEvent?.type, 'feedback'); - expect(envelopeEvent?.contexts.feedback?.toJson(), feedback.toJson()); - expect(envelopeEvent?.level, SentryLevel.info); - }); - - test('should cap feedback messages to max 4096 characters', () async { - final client = fixture.getSut(); - final feedback = fixture.fakeFeedback(); - feedback.message = 'a' * 4096 + 'b' * 4096; - await client.captureFeedback(feedback); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - expect(capturedEvent.contexts.feedback?.message, 'a' * 4096); - }); - }); - - group('SentryClient captureLog', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - SentryLog givenLog() { - return SentryLog( - timestamp: DateTime.now(), - traceId: SentryId.newId(), - level: SentryLogLevel.info, - body: 'test', - attributes: { - 'attribute': SentryLogAttribute.string('value'), - }, - ); - } - - test('sets log batcher on options when logs are enabled', () async { - expect(fixture.options.logBatcher is NoopLogBatcher, true); - - fixture.options.enableLogs = true; - fixture.getSut(); - - expect(fixture.options.logBatcher is NoopLogBatcher, false); - }); - - test('disabled by default', () async { - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - final log = givenLog(); - - await client.captureLog(log); - - final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; - expect(mockLogBatcher.addLogCalls, isEmpty); - }); - - test('should capture logs as envelope', () async { - fixture.options.enableLogs = true; - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - final log = givenLog(); - - await client.captureLog(log); - - final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; - expect(mockLogBatcher.addLogCalls.length, 1); - - final capturedLog = mockLogBatcher.addLogCalls.first; - - expect(capturedLog.traceId, log.traceId); - expect(capturedLog.level, log.level); - expect(capturedLog.body, log.body); - expect(capturedLog.attributes['attribute']?.value, - log.attributes['attribute']?.value); - }); - - test('should add additional info to attributes', () async { - fixture.options.enableLogs = true; - fixture.options.environment = 'test-environment'; - fixture.options.release = 'test-release'; - - final log = givenLog(); - - final scope = Scope(fixture.options); - final span = MockSpan(); - scope.span = span; - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - await client.captureLog(log, scope: scope); - - final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; - expect(mockLogBatcher.addLogCalls.length, 1); - final capturedLog = mockLogBatcher.addLogCalls.first; - - expect( - capturedLog.attributes['sentry.sdk.name']?.value, - fixture.options.sdk.name, - ); - expect( - capturedLog.attributes['sentry.sdk.name']?.type, - 'string', - ); - expect( - capturedLog.attributes['sentry.sdk.version']?.value, - fixture.options.sdk.version, - ); - expect( - capturedLog.attributes['sentry.sdk.version']?.type, - 'string', - ); - expect( - capturedLog.attributes['sentry.environment']?.value, - fixture.options.environment, - ); - expect( - capturedLog.attributes['sentry.environment']?.type, - 'string', - ); - expect( - capturedLog.attributes['sentry.release']?.value, - fixture.options.release, - ); - expect( - capturedLog.attributes['sentry.release']?.type, - 'string', - ); - expect( - capturedLog.attributes['sentry.trace.parent_span_id']?.value, - span.context.spanId.toString(), - ); - expect( - capturedLog.attributes['sentry.trace.parent_span_id']?.type, - 'string', - ); - }); - - test('should add user info to attributes', () async { - fixture.options.enableLogs = true; - - final log = givenLog(); - final scope = Scope(fixture.options); - final user = SentryUser( - id: '123', - email: 'test@test.com', - name: 'test-name', - ); - await scope.setUser(user); - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - await client.captureLog(log, scope: scope); - - final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; - expect(mockLogBatcher.addLogCalls.length, 1); - final capturedLog = mockLogBatcher.addLogCalls.first; - - expect( - capturedLog.attributes['user.id']?.value, - user.id, - ); - expect( - capturedLog.attributes['user.id']?.type, - 'string', - ); - - expect( - capturedLog.attributes['user.name']?.value, - user.name, - ); - expect( - capturedLog.attributes['user.name']?.type, - 'string', - ); - - expect( - capturedLog.attributes['user.email']?.value, - user.email, - ); - expect( - capturedLog.attributes['user.email']?.type, - 'string', - ); - }); - - test('should set trace id from propagation context', () async { - fixture.options.enableLogs = true; - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - final log = givenLog(); - final scope = Scope(fixture.options); - - await client.captureLog(log, scope: scope); - - final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; - expect(mockLogBatcher.addLogCalls.length, 1); - final capturedLog = mockLogBatcher.addLogCalls.first; - - expect(capturedLog.traceId, scope.propagationContext.traceId); - }); - - test( - '$BeforeSendLogCallback returning null drops the log and record it as lost', - () async { - fixture.options.enableLogs = true; - fixture.options.beforeSendLog = (log) => null; - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - final log = givenLog(); - - await client.captureLog(log); - - final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; - expect(mockLogBatcher.addLogCalls.length, 0); - - expect( - fixture.recorder.discardedEvents.first.reason, - DiscardReason.beforeSend, - ); - expect( - fixture.recorder.discardedEvents.first.category, - DataCategory.logItem, - ); - }); - - test('$BeforeSendLogCallback returning a log modifies it', () async { - fixture.options.enableLogs = true; - fixture.options.beforeSendLog = (log) { - log.body = 'modified'; - return log; - }; - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - final log = givenLog(); - - await client.captureLog(log); - - final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; - expect(mockLogBatcher.addLogCalls.length, 1); - final capturedLog = mockLogBatcher.addLogCalls.first; - - expect(capturedLog.body, 'modified'); - }); - - test('$BeforeSendLogCallback returning a log async modifies it', () async { - fixture.options.enableLogs = true; - fixture.options.beforeSendLog = (log) async { - await Future.delayed(Duration(milliseconds: 100)); - log.body = 'modified'; - return log; - }; - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - final log = givenLog(); - - await client.captureLog(log); - - final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; - expect(mockLogBatcher.addLogCalls.length, 1); - final capturedLog = mockLogBatcher.addLogCalls.first; - - expect(capturedLog.body, 'modified'); - }); - - test('$BeforeSendLogCallback throwing is caught', () async { - fixture.options.enableLogs = true; - fixture.options.automatedTestMode = false; - - fixture.options.beforeSendLog = (log) { - throw Exception('test'); - }; - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - final log = givenLog(); - await client.captureLog(log); - - final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; - expect(mockLogBatcher.addLogCalls.length, 1); - final capturedLog = mockLogBatcher.addLogCalls.first; - - expect(capturedLog.body, 'test'); - }); - - test('OnBeforeCaptureLog lifecycle event is called', () async { - fixture.options.enableLogs = true; - fixture.options.environment = 'test-environment'; - fixture.options.release = 'test-release'; - - final log = givenLog(); - - final scope = Scope(fixture.options); - final span = MockSpan(); - scope.span = span; - - final client = fixture.getSut(); - fixture.options.logBatcher = MockLogBatcher(); - - client.lifeCycleRegistry.registerCallback((event) { - event.log.attributes['test'] = SentryLogAttribute.string('test-value'); - }); - - await client.captureLog(log, scope: scope); - - final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; - expect(mockLogBatcher.addLogCalls.length, 1); - final capturedLog = mockLogBatcher.addLogCalls.first; - - expect(capturedLog.attributes['test']?.value, "test-value"); - expect(capturedLog.attributes['test']?.type, 'string'); - }); - }); - - group('SentryClient captures envelope', () { - late Fixture fixture; - final fakeEnvelope = getFakeEnvelope(); - - setUp(() { - fixture = Fixture(); - }); - - test('should capture envelope', () async { - final client = fixture.getSut(); - await client.captureEnvelope(fakeEnvelope); - - final capturedEnvelope = (fixture.transport).envelopes.first; - - expect(capturedEnvelope, fakeEnvelope); - }); - }); - - group('ClientReportTransport', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('set on options on init', () async { - fixture.getSut( - eventProcessor: DropAllEventProcessor(), - provideMockRecorder: false, - ); - - expect(fixture.options.transport is ClientReportTransport, true); - }); - - test('has rateLimiter with http transport', () async { - fixture.getSut( - eventProcessor: DropAllEventProcessor(), - provideMockRecorder: false, - transport: NoOpTransport(), // this will set http transport - ); - - expect(fixture.options.transport is ClientReportTransport, true); - final crt = fixture.options.transport as ClientReportTransport; - expect(crt.rateLimiter, isNotNull); - }); - - test('does not have rateLimiter without http transport', () async { - fixture.getSut( - eventProcessor: DropAllEventProcessor(), - provideMockRecorder: false, - transport: MockTransport(), - ); - - expect(fixture.options.transport is ClientReportTransport, true); - final crt = fixture.options.transport as ClientReportTransport; - expect(crt.rateLimiter, isNull); - }); - }); - - group('ClientReportRecorder', () { - late Fixture fixture; - late SentryEvent fakeEvent; - - setUp(() { - fixture = Fixture(); - fakeEvent = getFakeEvent(); - }); - - test('recorder is not noop if client reports are enabled', () async { - fixture.options.sendClientReports = true; - - fixture.getSut( - eventProcessor: DropAllEventProcessor(), - provideMockRecorder: false, - ); - - expect(fixture.options.recorder is NoOpClientReportRecorder, false); - }); - - test('recorder is noop if client reports are disabled', () { - fixture.options.sendClientReports = false; - - fixture.getSut( - eventProcessor: DropAllEventProcessor(), - provideMockRecorder: false, - ); - - expect(fixture.options.recorder is NoOpClientReportRecorder, true); - }); - - test('record event processor dropping event', () async { - bool secondProcessorCalled = false; - fixture.options.addEventProcessor(DropAllEventProcessor()); - fixture.options.addEventProcessor(FunctionEventProcessor((event, hint) { - secondProcessorCalled = true; - return event; - })); - final client = fixture.getSut(); - - await client.captureEvent(fakeEvent); - - expect(fixture.recorder.discardedEvents.first.reason, - DiscardReason.eventProcessor); - expect( - fixture.recorder.discardedEvents.first.category, DataCategory.error); - expect(secondProcessorCalled, isFalse); - }); - - test('record event processor dropping transaction', () async { - final sut = fixture.getSut(eventProcessor: DropAllEventProcessor()); - final transaction = SentryTransaction(fixture.tracer); - fixture.tracer.startChild('child1'); - fixture.tracer.startChild('child2'); - fixture.tracer.startChild('child3'); - - await sut.captureTransaction(transaction); - - expect(fixture.recorder.discardedEvents.length, 2); - - final spanCount = fixture.recorder.discardedEvents - .firstWhere((element) => - element.category == DataCategory.span && - element.reason == DiscardReason.eventProcessor) - .quantity; - expect(spanCount, 4); - }); - - test('record event processor dropping feedback', () async { - final client = fixture.getSut(eventProcessor: DropAllEventProcessor()); - final feedback = fixture.fakeFeedback(); - await client.captureFeedback(feedback); - - expect(fixture.recorder.discardedEvents.first.category, - DataCategory.feedback); - expect(fixture.recorder.discardedEvents.first.reason, - DiscardReason.eventProcessor); - }); - - test('record event processor dropping partially spans', () async { - final numberOfSpansDropped = 2; - final sut = fixture.getSut( - eventProcessor: DropSpansEventProcessor(numberOfSpansDropped)); - final transaction = SentryTransaction(fixture.tracer); - fixture.tracer.startChild('child1'); - fixture.tracer.startChild('child2'); - fixture.tracer.startChild('child3'); - - await sut.captureTransaction(transaction); - - expect(fixture.recorder.discardedEvents.length, 1); - - final spanCount = fixture.recorder.discardedEvents - .firstWhere((element) => - element.category == DataCategory.span && - element.reason == DiscardReason.eventProcessor) - .quantity; - expect(spanCount, numberOfSpansDropped); - }); - - test('beforeSendTransaction correctly records partially dropped spans', - () async { - final sut = fixture.getSut(); - final transaction = SentryTransaction(fixture.tracer); - fixture.tracer.startChild('child1'); - fixture.tracer.startChild('child2'); - fixture.tracer.startChild('child3'); - - fixture.options.beforeSendTransaction = (transaction, hint) { - if (transaction.tracer == fixture.tracer) { - return null; - } - return transaction; - }; - - await sut.captureTransaction(transaction); - - expect(fixture.recorder.discardedEvents.length, 2); - - final spanCount = fixture.recorder.discardedEvents - .firstWhere((element) => - element.category == DataCategory.span && - element.reason == DiscardReason.beforeSend) - .quantity; - expect(spanCount, 4); - }); - - test('beforeSendTransaction correctly records partially dropped spans', - () async { - final sut = fixture.getSut(); - final transaction = SentryTransaction(fixture.tracer); - fixture.tracer.startChild('child1'); - fixture.tracer.startChild('child2'); - fixture.tracer.startChild('child3'); - - fixture.options.beforeSendTransaction = (transaction, hint) { - if (transaction.tracer == fixture.tracer) { - transaction.spans - .removeWhere((element) => element.context.operation == 'child2'); - return transaction; - } - return transaction; - }; - - await sut.captureTransaction(transaction); - - // we didn't drop the whole transaction, we only have 1 event for the dropped spans - expect(fixture.recorder.discardedEvents.length, 1); - - // tracer has 3 span children and we dropped 1 of them - final spanCount = fixture.recorder.discardedEvents - .firstWhere((element) => - element.category == DataCategory.span && - element.reason == DiscardReason.beforeSend) - .quantity; - expect(spanCount, 1); - }); - - test('record event processor dropping transaction', () async { - final client = fixture.getSut(eventProcessor: DropAllEventProcessor()); - - final context = SentryTransactionContext('name', 'op'); - final tracer = SentryTracer(context, MockHub()); - final transaction = SentryTransaction(tracer); - - await client.captureTransaction(transaction); - - expect(fixture.recorder.discardedEvents.first.reason, - DiscardReason.eventProcessor); - expect(fixture.recorder.discardedEvents.first.category, - DataCategory.transaction); - }); - - test('record beforeSend dropping event', () async { - final client = fixture.getSut(); - - fixture.options.beforeSend = fixture.droppingBeforeSend; - - await client.captureEvent(fakeEvent); - - expect(fixture.recorder.discardedEvents.first.reason, - DiscardReason.beforeSend); - expect( - fixture.recorder.discardedEvents.first.category, DataCategory.error); - }); - - test('record beforeSend dropping feedback', () async { - final client = fixture.getSut(); - - fixture.options.beforeSendFeedback = fixture.droppingBeforeSend; - - final feedback = fixture.fakeFeedback(); - await client.captureFeedback(feedback); - - expect(fixture.recorder.discardedEvents.first.reason, - DiscardReason.beforeSend); - expect(fixture.recorder.discardedEvents.first.category, - DataCategory.feedback); - }); - - test('record sample rate dropping event', () async { - final client = fixture.getSut(sampleRate: 0.0); - - fixture.options.beforeSend = fixture.droppingBeforeSend; - - await client.captureEvent(fakeEvent); - - expect(fixture.recorder.discardedEvents.first.reason, - DiscardReason.sampleRate); - expect( - fixture.recorder.discardedEvents.first.category, DataCategory.error); - }); - - test('record sample rate not dropping feedback', () async { - final client = fixture.getSut(sampleRate: 0.0); - - await client.captureFeedback(fixture.fakeFeedback()); - - expect(fixture.recorder.discardedEvents.isEmpty, true); - }); - }); - - group('Spotlight', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test( - 'Spotlight enabled should not set transport to SpotlightHttpTransport on iOS', - () async { - fixture.options.platform = MockPlatform.iOS(); - fixture.options.spotlight = Spotlight(enabled: true); - fixture.getSut(); - - expect(fixture.options.transport is SpotlightHttpTransport, isFalse); - }); - - test( - 'Spotlight enabled should not set transport to SpotlightHttpTransport on macOS', - () async { - fixture.options.platform = MockPlatform.macOS(); - fixture.options.spotlight = Spotlight(enabled: true); - fixture.getSut(); - - expect(fixture.options.transport is SpotlightHttpTransport, isFalse); - }); - - test( - 'Spotlight enabled should not set transport to SpotlightHttpTransport on Android', - () async { - fixture.options.platform = MockPlatform.android(); - fixture.options.spotlight = Spotlight(enabled: true); - fixture.getSut(); - - expect(fixture.options.transport is SpotlightHttpTransport, isFalse); - }); - - test( - 'Spotlight enabled should set transport to SpotlightHttpTransport on Web', - () async { - fixture.options.platform = MockPlatform(isWeb: true); - fixture.options.spotlight = Spotlight(enabled: true); - fixture.getSut(); - - expect(fixture.options.transport is SpotlightHttpTransport, isTrue); - }); - - test( - 'Spotlight enabled should set transport to SpotlightHttpTransport on Linux', - () async { - fixture.options.platform = MockPlatform.linux(); - fixture.options.spotlight = Spotlight(enabled: true); - fixture.getSut(); - - expect(fixture.options.transport is SpotlightHttpTransport, isTrue); - }); - - test( - 'Spotlight enabled should set transport to SpotlightHttpTransport on Windows', - () async { - fixture.options.platform = MockPlatform.windows(); - fixture.options.spotlight = Spotlight(enabled: true); - fixture.getSut(); - - expect(fixture.options.transport is SpotlightHttpTransport, isTrue); - }); - }); - - group('trace context', () { - late Fixture fixture; - late SentryEvent fakeEvent; - - setUp(() { - fixture = Fixture(); - fakeEvent = getFakeEvent(); - }); - - test('captureEvent adds trace context', () async { - final client = fixture.getSut(); - - final scope = Scope(fixture.options); - scope.replayId = SentryId.newId(); - scope.span = - SentrySpan(fixture.tracer, fixture.tracer.context, MockHub()); - - await client.captureEvent(fakeEvent, scope: scope); - - final envelope = fixture.transport.envelopes.first; - expect(envelope.header.traceContext, isNotNull); - expect(envelope.header.traceContext?.replayId, scope.replayId); - }); - - test('captureTransaction adds trace context', () async { - final client = fixture.getSut(); - - final tr = SentryTransaction(fixture.tracer); - - final context = SentryTraceContextHeader.fromJson({ - 'trace_id': '${tr.eventId}', - 'public_key': '123', - 'replay_id': '456', - }); - - await client.captureTransaction(tr, traceContext: context); - - final envelope = fixture.transport.envelopes.first; - expect(envelope.header.traceContext, isNotNull); - expect(envelope.header.traceContext?.replayId, SentryId.fromId('456')); - }); - - test('captureFeedback adds trace context', () async { - final client = fixture.getSut(); - - final scope = Scope(fixture.options); - scope.span = - SentrySpan(fixture.tracer, fixture.tracer.context, MockHub()); - - await client.captureFeedback(fixture.fakeFeedback(), scope: scope); - - final envelope = fixture.transport.envelopes.first; - expect(envelope.header.traceContext, isNotNull); - }); - }); - - group('Hint', () { - late Fixture fixture; - late SentryEvent fakeEvent; - - setUp(() { - fixture = Fixture(); - fakeEvent = getFakeEvent(); - }); - - test('captureEvent adds attachments from hint', () async { - final attachment = SentryAttachment.fromIntList([], "fixture-fileName"); - final hint = Hint.withAttachment(attachment); - - final sut = fixture.getSut(); - await sut.captureEvent(fakeEvent, hint: hint); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final attachmentItem = IterableUtils.firstWhereOrNull( - capturedEnvelope.items, - (SentryEnvelopeItem e) => e.header.type == SentryItemType.attachment, - ); - expect(attachmentItem?.header.attachmentType, - SentryAttachment.typeAttachmentDefault); - }); - - test('captureFeedback adds attachments from hint', () async { - final attachment = SentryAttachment.fromIntList([], "fixture-fileName"); - final hint = Hint.withAttachment(attachment); - - final sut = fixture.getSut(); - final fakeFeedback = fixture.fakeFeedback(); - await sut.captureFeedback(fakeFeedback, hint: hint); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final attachmentItem = IterableUtils.firstWhereOrNull( - capturedEnvelope.items, - (SentryEnvelopeItem e) => e.header.type == SentryItemType.attachment, - ); - expect(attachmentItem?.header.attachmentType, - SentryAttachment.typeAttachmentDefault); - }); - - test('captureTransaction hint passed to beforeSendTransaction', () async { - final sut = fixture.getSut(); - - final hint = Hint(); - final transaction = SentryTransaction(fixture.tracer); - - fixture.options.beforeSendTransaction = (bsTransaction, bsHint) async { - expect(hint, bsHint); - return bsTransaction; - }; - - await sut.captureTransaction(transaction, hint: hint); - }); - - test('captureTransaction hint passed to event processors', () async { - final hint = Hint(); - - final eventProcessor = FunctionEventProcessor((event, epHint) { - expect(epHint, hint); - return event; - }); - final sut = fixture.getSut(eventProcessor: eventProcessor); - - final transaction = SentryTransaction(fixture.tracer); - await sut.captureTransaction(transaction, hint: hint); - }); - - test('captureEvent adds screenshot from hint', () async { - final client = fixture.getSut(); - final screenshot = - SentryAttachment.fromScreenshotData(Uint8List.fromList([0, 0, 0, 0])); - final hint = Hint.withScreenshot(screenshot); - - await client.captureEvent(fakeEvent, hint: hint); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final attachmentItem = capturedEnvelope.items.firstWhereOrNull( - (element) => element.header.type == SentryItemType.attachment); - expect(attachmentItem?.header.fileName, 'screenshot.png'); - }); - - test('captureFeedback adds screenshot from hint', () async { - final client = fixture.getSut(); - final screenshot = - SentryAttachment.fromScreenshotData(Uint8List.fromList([0, 0, 0, 0])); - final hint = Hint.withScreenshot(screenshot); - - final fakeFeedback = fixture.fakeFeedback(); - await client.captureFeedback(fakeFeedback, hint: hint); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final attachmentItem = capturedEnvelope.items.firstWhereOrNull( - (element) => element.header.type == SentryItemType.attachment); - expect(attachmentItem?.header.fileName, 'screenshot.png'); - }); - - test('captureEvent adds viewHierarchy from hint', () async { - final client = fixture.getSut(); - final view = SentryViewHierarchy('flutter'); - final attachment = SentryAttachment.fromViewHierarchy(view); - final hint = Hint.withViewHierarchy(attachment); - - await client.captureEvent(fakeEvent, hint: hint); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final attachmentItem = capturedEnvelope.items.firstWhereOrNull( - (element) => element.header.type == SentryItemType.attachment); - - expect(attachmentItem?.header.attachmentType, - SentryAttachment.typeViewHierarchy); - }); - - test('captureFeedback does not add viewHierarchy from hint', () async { - final client = fixture.getSut(); - final view = SentryViewHierarchy('flutter'); - final attachment = SentryAttachment.fromViewHierarchy(view); - final hint = Hint.withViewHierarchy(attachment); - - final fakeFeedback = fixture.fakeFeedback(); - await client.captureFeedback(fakeFeedback, hint: hint); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final attachmentItem = capturedEnvelope.items.firstWhereOrNull( - (element) => element.header.type == SentryItemType.attachment, - ); - expect(attachmentItem, isNull); - }); - - test( - 'null stack trace marked in hint & sentry frames removed from thread stackTrace', - () async { - final beforeSendCallback = (SentryEvent event, Hint hint) { - expect(hint.get(TypeCheckHint.currentStackTrace), isTrue); - return event; - }; - final client = fixture.getSut( - beforeSend: beforeSendCallback, attachStacktrace: true); - await client.captureEvent(fakeEvent); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - final sentryFramesCount = capturedEvent.threads?[0].stacktrace!.frames - .where((frame) => frame.package == 'sentry') - .length; - - expect(sentryFramesCount, 0); - }); - - test( - 'empty stack trace marked in hint & sentry frames removed from thread stackTrace', - () async { - final beforeSendCallback = (SentryEvent event, Hint hint) { - expect(hint.get(TypeCheckHint.currentStackTrace), isTrue); - return event; - }; - final client = fixture.getSut( - beforeSend: beforeSendCallback, attachStacktrace: true); - await client.captureEvent(fakeEvent, stackTrace: StackTrace.empty); - - final capturedEnvelope = (fixture.transport).envelopes.first; - final capturedEvent = await eventFromEnvelope(capturedEnvelope); - - final sentryFramesCount = capturedEvent.threads?[0].stacktrace!.frames - .where((frame) => frame.package == 'sentry') - .length; - - expect(sentryFramesCount, 0); - }); - - test('non-null stack trace not marked in hint', () async { - final beforeSendCallback = (SentryEvent event, Hint hint) { - expect(hint.get(TypeCheckHint.currentStackTrace), isNull); - return event; - }; - final client = fixture.getSut( - beforeSend: beforeSendCallback, attachStacktrace: true); - await client.captureEvent(fakeEvent, stackTrace: StackTrace.current); - }); - }); -} - -Future eventFromEnvelope(SentryEnvelope envelope) async { - final data = await envelope.items.first.dataFactory(); - final utf8Data = utf8.decode(data); - final envelopeItemJson = jsonDecode(utf8Data); - return SentryEvent.fromJson(envelopeItemJson as Map); -} - -Future> transactionFromEnvelope( - SentryEnvelope envelope) async { - final data = await envelope.items.first.dataFactory(); - final utf8Data = utf8.decode(data); - final envelopeItemJson = jsonDecode(utf8Data); - return envelopeItemJson as Map; -} - -SentryEvent? beforeSendCallbackDropEvent( - SentryEvent event, - Hint hint, -) => - null; - -SentryTransaction? beforeSendFeedbackCallbackDropEvent( - SentryEvent feedbackEvent, - Hint hint, -) => - null; - -Future asyncBeforeSendFeedbackCallbackDropEvent( - SentryEvent feedbackEvent, - Hint hint, -) async { - await Future.delayed(Duration(milliseconds: 200)); - return null; -} - -SentryTransaction? beforeSendTransactionCallbackDropEvent( - SentryTransaction event, - Hint hint, -) => - null; - -Future asyncBeforeSendCallbackDropEvent( - SentryEvent event, - Hint hint, -) async { - await Future.delayed(Duration(milliseconds: 200)); - return null; -} - -Future asyncBeforeSendTransactionCallbackDropEvent( - SentryEvent event, - Hint hint, -) async { - await Future.delayed(Duration(milliseconds: 200)); - return null; -} - -SentryEvent? beforeSendFeedbackCallback(SentryEvent event, Hint hint) { - event.tags = {'theme': 'material'}; - return event; -} - -SentryEvent? beforeSendCallback(SentryEvent event, Hint hint) { - return event - ..tags!.addAll({'theme': 'material'}) - // ignore: deprecated_member_use_from_same_package - ..extra!['host'] = '0.0.0.1' - ..modules!.addAll({'core': '1.0'}) - ..breadcrumbs!.add(Breadcrumb(message: 'processor crumb')) - ..fingerprint!.add('process') - ..sdk!.addIntegration('testIntegration') - ..sdk!.addPackage('test-pkg', '1.0'); -} - -SentryTransaction? beforeSendTransactionCallback( - SentryTransaction transaction, - Hint hint, -) { - return transaction - ..tags!.addAll({'theme': 'material'}) - // ignore: deprecated_member_use_from_same_package - ..extra!['host'] = '0.0.0.1' - ..sdk!.addIntegration('testIntegration') - ..sdk!.addPackage('test-pkg', '1.0') - ..breadcrumbs!.add(Breadcrumb(message: 'processor crumb')); -} - -class Fixture { - final recorder = MockClientReportRecorder(); - final transport = MockTransport(); - - final options = defaultTestOptions() - ..platform = MockPlatform.iOS() - ..groupExceptions = true; - - late SentryTransactionContext _context; - late SentryTracer tracer; - - SentryLevel? loggedLevel; - Object? loggedException; - - SentryClient getSut({ - bool sendDefaultPii = false, - bool attachStacktrace = true, - bool attachThreads = false, - double? sampleRate, - BeforeSendCallback? beforeSend, - BeforeSendTransactionCallback? beforeSendTransaction, - BeforeSendCallback? beforeSendFeedback, - EventProcessor? eventProcessor, - bool provideMockRecorder = true, - bool debug = false, - Transport? transport, - }) { - options.tracesSampleRate = 1.0; - options.sendDefaultPii = sendDefaultPii; - options.attachStacktrace = attachStacktrace; - options.attachThreads = attachThreads; - options.sampleRate = sampleRate; - options.beforeSend = beforeSend; - options.beforeSendTransaction = beforeSendTransaction; - options.beforeSendFeedback = beforeSendFeedback; - options.debug = debug; - options.log = mockLogger; - - if (eventProcessor != null) { - options.addEventProcessor(eventProcessor); - } - - // Internally also creates a SentryClient instance - final hub = Hub(options); - _context = SentryTransactionContext( - 'name', - 'op', - ); - tracer = SentryTracer(_context, hub); - - // Reset transport - options.transport = transport ?? this.transport; - - // Again create SentryClient instance - final client = SentryClient(options); - - if (provideMockRecorder) { - options.recorder = recorder; - } - return client; - } - - Future droppingBeforeSend(SentryEvent event, Hint hint) async { - return null; - } - - SentryTransaction fakeTransaction() { - return SentryTransaction( - tracer, - sdk: SdkVersion(name: 'sdk1', version: '1.0.0'), - breadcrumbs: [], - ); - } - - SentryEvent fakeFeedbackEvent() { - return SentryEvent( - type: 'feedback', - contexts: Contexts(feedback: fakeFeedback()), - level: SentryLevel.info, - ); - } - - SentryFeedback fakeFeedback() { - return SentryFeedback( - message: 'fixture-message', - contactEmail: 'fixture-contactEmail', - name: 'fixture-name', - replayId: 'fixture-replayId', - url: "https://fixture-url.com", - associatedEventId: SentryId.fromId('1d49af08b6e2c437f9052b1ecfd83dca'), - ); - } - - void mockLogger( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - loggedLevel = level; - loggedException = exception; - } -} - -class ExceptionWithCause { - ExceptionWithCause(this.cause, this.stackTrace); - - final dynamic cause; - final dynamic stackTrace; -} - -class ExceptionWithCauseExtractor - extends ExceptionCauseExtractor { - @override - ExceptionCause? cause(ExceptionWithCause error) { - return ExceptionCause(error.cause, error.stackTrace, source: "cause"); - } -} - -class ExceptionWithStackTrace { - ExceptionWithStackTrace(this.stackTrace); - - final StackTrace stackTrace; -} - -class ExceptionWithStackTraceExtractor - extends ExceptionStackTraceExtractor { - @override - StackTrace? stackTrace(ExceptionWithStackTrace error) { - return error.stackTrace; - } -} diff --git a/dart/test/sentry_envelope_header_test.dart b/dart/test/sentry_envelope_header_test.dart deleted file mode 100644 index cc10f97434..0000000000 --- a/dart/test/sentry_envelope_header_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/sentry_envelope_header.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; - -void main() { - group('SentryEnvelopeHeader', () { - test('toJson empty', () { - final sut = SentryEnvelopeHeader(null, null); - final expected = {}; - expect(sut.toJson(), expected); - }); - - test('toJson', () async { - final eventId = SentryId.newId(); - final sdkVersion = SdkVersion( - name: 'fixture-sdkName', - version: 'fixture-version', - ); - final context = SentryTraceContextHeader.fromJson({ - 'trace_id': '${SentryId.newId()}', - 'public_key': '123', - }); - final timestamp = DateTime.utc(2019); - final sut = SentryEnvelopeHeader( - eventId, - sdkVersion, - dsn: fakeDsn, - traceContext: context, - sentAt: timestamp, - ); - final expextedSkd = sdkVersion.toJson(); - final expected = { - 'event_id': eventId.toString(), - 'sdk': expextedSkd, - 'trace': context.toJson(), - 'dsn': fakeDsn, - 'sent_at': formatDateAsIso8601WithMillisPrecision(timestamp), - }; - expect(sut.toJson(), expected); - }); - }); -} diff --git a/dart/test/sentry_envelope_item_header_test.dart b/dart/test/sentry_envelope_item_header_test.dart deleted file mode 100644 index b948568ed9..0000000000 --- a/dart/test/sentry_envelope_item_header_test.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:sentry/src/sentry_envelope_item_header.dart'; -import 'package:sentry/src/sentry_item_type.dart'; -import 'package:test/test.dart'; - -void main() { - group('SentryEnvelopeItemHeader', () { - test('serialize', () async { - final sut = SentryEnvelopeItemHeader(SentryItemType.event, - itemCount: 3, contentType: 'application/json'); - final expected = { - 'item_count': 3, - 'content_type': 'application/json', - 'type': 'event', - 'length': 3 - }; - expect(await sut.toJson(3), expected); - }); - }); -} diff --git a/dart/test/sentry_envelope_item_test.dart b/dart/test/sentry_envelope_item_test.dart deleted file mode 100644 index 741773a66a..0000000000 --- a/dart/test/sentry_envelope_item_test.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'dart:convert'; - -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/client_reports/client_report.dart'; -import 'package:sentry/src/client_reports/discard_reason.dart'; -import 'package:sentry/src/client_reports/discarded_event.dart'; -import 'package:sentry/src/sentry_item_type.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:sentry/src/transport/data_category.dart'; -import 'package:test/test.dart'; - -import 'mocks/mock_hub.dart'; - -void main() { - group('SentryEnvelopeItem', () { - test('fromEvent', () async { - final eventId = SentryId.newId(); - final sentryEvent = SentryEvent(eventId: eventId); - final sut = SentryEnvelopeItem.fromEvent(sentryEvent); - - final expectedData = utf8.encode(jsonEncode( - sentryEvent.toJson(), - toEncodable: jsonSerializationFallback, - )); - final actualData = await sut.dataFactory(); - - expect(sut.header.contentType, 'application/json'); - expect(sut.header.type, SentryItemType.event); - expect(actualData, expectedData); - }); - - test('fromEvent feedback', () async { - final feedback = SentryFeedback( - message: 'fixture-message', - ); - final feedbackEvent = SentryEvent( - type: 'feedback', - contexts: Contexts(feedback: feedback), - level: SentryLevel.info, - ); - final sut = SentryEnvelopeItem.fromEvent(feedbackEvent); - - final expectedData = utf8.encode(jsonEncode( - feedbackEvent.toJson(), - toEncodable: jsonSerializationFallback, - )); - final actualData = await sut.dataFactory(); - - expect(sut.header.contentType, 'application/json'); - expect(sut.header.type, 'feedback'); - expect(actualData, expectedData); - }); - - test('fromTransaction', () async { - final context = SentryTransactionContext( - 'name', - 'op', - ); - final tracer = SentryTracer(context, MockHub()); - final tr = SentryTransaction(tracer); - tr.contexts.device = SentryDevice( - orientation: SentryOrientation.landscape, - ); - - final sut = SentryEnvelopeItem.fromTransaction(tr); - - final expectedData = utf8.encode(jsonEncode( - tr.toJson(), - toEncodable: jsonSerializationFallback, - )); - final actualData = await sut.dataFactory(); - - expect(sut.header.contentType, 'application/json'); - expect(sut.header.type, SentryItemType.transaction); - expect(actualData, expectedData); - }); - - test('fromClientReport', () async { - final timestamp = DateTime(0); - final discardedEvents = [ - DiscardedEvent(DiscardReason.rateLimitBackoff, DataCategory.error, 1) - ]; - - final cr = ClientReport(timestamp, discardedEvents); - - final sut = SentryEnvelopeItem.fromClientReport(cr); - - final expectedData = utf8.encode(jsonEncode( - cr.toJson(), - toEncodable: jsonSerializationFallback, - )); - final actualData = await sut.dataFactory(); - - expect(sut.header.contentType, 'application/json'); - expect(sut.header.type, SentryItemType.clientReport); - expect(actualData, expectedData); - }); - - test('fromLog', () async { - final logs = [ - SentryLog( - timestamp: DateTime.now(), - traceId: SentryId.newId(), - level: SentryLogLevel.info, - body: 'test', - attributes: { - 'test': SentryLogAttribute.string('test'), - }, - ), - SentryLog( - timestamp: DateTime.now(), - traceId: SentryId.newId(), - level: SentryLogLevel.info, - body: 'test2', - attributes: { - 'test2': SentryLogAttribute.int(9001), - }, - ), - ]; - - final sut = SentryEnvelopeItem.fromLogs(logs); - - final expectedData = utf8.encode(jsonEncode( - { - 'items': logs.map((e) => e.toJson()).toList(), - }, - toEncodable: jsonSerializationFallback, - )); - final actualData = await sut.dataFactory(); - - expect(sut.header.contentType, 'application/vnd.sentry.items.log+json'); - expect(sut.header.type, SentryItemType.log); - expect(actualData, expectedData); - }); - }); -} diff --git a/dart/test/sentry_envelope_test.dart b/dart/test/sentry_envelope_test.dart deleted file mode 100644 index 9a21339aaa..0000000000 --- a/dart/test/sentry_envelope_test.dart +++ /dev/null @@ -1,276 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/sentry_envelope_header.dart'; -import 'package:sentry/src/sentry_envelope_item_header.dart'; -import 'package:sentry/src/sentry_item_type.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'mocks/mock_hub.dart'; -import 'test_utils.dart'; - -void main() { - group('SentryEnvelope', () { - Future serializedItem(SentryEnvelopeItem item) async { - final expectedItemData = await item.dataFactory(); - final expectedItemHeader = utf8JsonEncoder - .convert(await item.header.toJson(expectedItemData.length)); - final newLine = utf8.encode('\n'); - final expectedItem = [ - ...expectedItemHeader, - ...newLine, - ...expectedItemData - ]; - return utf8.decode(expectedItem); - } - - test('serialize', () async { - final eventId = SentryId.newId(); - - final itemHeader = SentryEnvelopeItemHeader(SentryItemType.event, - contentType: 'application/json'); - - final dataFactory = () async { - return utf8.encode('{fixture}'); - }; - - final item = SentryEnvelopeItem(itemHeader, dataFactory); - - final context = SentryTraceContextHeader.fromJson({ - 'trace_id': '${SentryId.newId()}', - 'public_key': '123', - }); - final header = SentryEnvelopeHeader( - eventId, - null, - traceContext: context, - ); - final sut = SentryEnvelope(header, [item, item]); - - final expectedHeaderJson = header.toJson(); - final expectedHeaderJsonSerialized = jsonEncode( - expectedHeaderJson, - toEncodable: jsonSerializationFallback, - ); - - final expectedItemSerialized = await serializedItem(item); - - final expected = utf8.encode( - '$expectedHeaderJsonSerialized\n$expectedItemSerialized\n$expectedItemSerialized'); - - final envelopeData = []; - await sut - .envelopeStream(defaultTestOptions()) - .forEach(envelopeData.addAll); - expect(envelopeData, expected); - }); - - test('fromEvent', () async { - final eventId = SentryId.newId(); - final sentryEvent = SentryEvent(eventId: eventId); - final sdkVersion = - SdkVersion(name: 'fixture-name', version: 'fixture-version'); - final context = SentryTraceContextHeader.fromJson({ - 'trace_id': '${SentryId.newId()}', - 'public_key': '123', - }); - final sut = SentryEnvelope.fromEvent( - sentryEvent, - sdkVersion, - dsn: fakeDsn, - traceContext: context, - ); - - final expectedEnvelopeItem = SentryEnvelopeItem.fromEvent(sentryEvent); - - expect(sut.header.eventId, eventId); - expect(sut.header.sdkVersion, sdkVersion); - expect(sut.header.traceContext, context); - expect(sut.header.dsn, fakeDsn); - expect(sut.items[0].header.contentType, - expectedEnvelopeItem.header.contentType); - expect(sut.items[0].header.type, expectedEnvelopeItem.header.type); - - final actualItem = await sut.items[0].dataFactory(); - final expectedItem = await expectedEnvelopeItem.dataFactory(); - expect(actualItem, expectedItem); - }); - - test('fromTransaction', () async { - final context = SentryTransactionContext( - 'name', - 'op', - ); - final tracer = SentryTracer(context, MockHub()); - final tr = SentryTransaction(tracer); - - final sdkVersion = - SdkVersion(name: 'fixture-name', version: 'fixture-version'); - final traceContext = SentryTraceContextHeader.fromJson({ - 'trace_id': '${SentryId.newId()}', - 'public_key': '123', - }); - final sut = SentryEnvelope.fromTransaction( - tr, - sdkVersion, - dsn: fakeDsn, - traceContext: traceContext, - ); - - final expectedEnvelopeItem = SentryEnvelopeItem.fromTransaction(tr); - - expect(sut.header.eventId, tr.eventId); - expect(sut.header.sdkVersion, sdkVersion); - expect(sut.header.traceContext, traceContext); - expect(sut.header.dsn, fakeDsn); - expect(sut.items[0].header.contentType, - expectedEnvelopeItem.header.contentType); - expect(sut.items[0].header.type, expectedEnvelopeItem.header.type); - - final actualItem = await sut.items[0].dataFactory(); - final expectedItem = await expectedEnvelopeItem.dataFactory(); - expect(actualItem, expectedItem); - }); - - test('fromLogs', () async { - final logs = [ - SentryLog( - timestamp: DateTime.now(), - traceId: SentryId.newId(), - level: SentryLogLevel.info, - body: 'test', - attributes: { - 'test': SentryLogAttribute.string('test'), - }, - ), - SentryLog( - timestamp: DateTime.now(), - traceId: SentryId.newId(), - level: SentryLogLevel.info, - body: 'test2', - attributes: { - 'test2': SentryLogAttribute.int(9001), - }, - ), - ]; - - final sdkVersion = SdkVersion( - name: 'fixture-name', - version: 'fixture-version', - ); - - final sut = SentryEnvelope.fromLogs(logs, sdkVersion); - - expect(sut.header.sdkVersion, sdkVersion); - - final expectedItem = SentryEnvelopeItem.fromLogs(logs); - final expectedItemData = await expectedItem.dataFactory(); - final actualItemData = await sut.items[0].dataFactory(); - - expect(actualItemData, expectedItemData); - }); - - test('max attachment size', () async { - final attachment = SentryAttachment.fromLoader( - loader: () => Uint8List.fromList([1, 2, 3, 4]), - filename: 'test.txt', - ); - - final eventId = SentryId.newId(); - final sentryEvent = SentryEvent(eventId: eventId); - final sdkVersion = - SdkVersion(name: 'fixture-name', version: 'fixture-version'); - - final sut = SentryEnvelope.fromEvent( - sentryEvent, - sdkVersion, - dsn: fakeDsn, - attachments: [attachment], - ); - - final expectedEnvelopeItem = SentryEnvelope.fromEvent( - sentryEvent, - sdkVersion, - dsn: fakeDsn, - ); - - final sutEnvelopeData = []; - await sut - .envelopeStream(defaultTestOptions()..maxAttachmentSize = 1) - .forEach(sutEnvelopeData.addAll); - - final envelopeData = []; - await expectedEnvelopeItem - .envelopeStream(defaultTestOptions()) - .forEach(envelopeData.addAll); - - expect(sutEnvelopeData, envelopeData); - }); - - test('ignore throwing envelope items', () async { - final eventId = SentryId.newId(); - - final itemHeader = SentryEnvelopeItemHeader(SentryItemType.event, - contentType: 'application/json'); - final dataFactory = () async { - return utf8.encode('{fixture}'); - }; - final dataFactoryThrowing = () async { - throw Exception('Exception in data factory.'); - }; - - final item = SentryEnvelopeItem(itemHeader, dataFactory); - final throwingItem = SentryEnvelopeItem(itemHeader, dataFactoryThrowing); - - final context = SentryTraceContextHeader.fromJson({ - 'trace_id': '${SentryId.newId()}', - 'public_key': '123', - }); - final header = SentryEnvelopeHeader( - eventId, - null, - traceContext: context, - ); - final sut = SentryEnvelope(header, [item, throwingItem]); - - final expectedHeaderJson = header.toJson(); - final expectedHeaderJsonSerialized = jsonEncode( - expectedHeaderJson, - toEncodable: jsonSerializationFallback, - ); - - final expectedItemSerialized = await serializedItem(item); - - final expected = - utf8.encode('$expectedHeaderJsonSerialized\n$expectedItemSerialized'); - - final options = defaultTestOptions(); - options.automatedTestMode = false; // Test if throwing item is ignored. - final envelopeData = []; - await sut.envelopeStream(options).forEach(envelopeData.addAll); - expect(envelopeData, expected); - }); - - // This test passes if no exceptions are thrown, thus no asserts. - // This is a test for https://github.com/getsentry/sentry-dart/issues/523 - test('serialize with non-serializable class', () async { - // ignore: deprecated_member_use_from_same_package - final event = SentryEvent(extra: {'non-encodable': NonEncodable()}); - final sut = SentryEnvelope.fromEvent( - event, - SdkVersion( - name: 'test', - version: '1', - ), - dsn: fakeDsn, - ); - - final _ = sut.envelopeStream(defaultTestOptions()).map((e) => e); - }); - }); -} - -class NonEncodable {} diff --git a/dart/test/sentry_envelope_vm_test.dart b/dart/test/sentry_envelope_vm_test.dart deleted file mode 100644 index 96da255234..0000000000 --- a/dart/test/sentry_envelope_vm_test.dart +++ /dev/null @@ -1,78 +0,0 @@ -@TestOn('vm') -library; - -import 'dart:convert'; -import 'dart:io'; - -import 'package:sentry/sentry_io.dart'; -import 'package:sentry/src/sentry_envelope_header.dart'; -import 'package:sentry/src/sentry_envelope_item_header.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'test_utils.dart'; - -void main() { - group('SentryEnvelopeItem', () { - test('item with binary payload', () async { - // Attachment - - // length == 3535 - final dataFactory = () async { - final file = File('test_resources/sentry.png'); - final bytes = await file.readAsBytes(); - return bytes; - }; - final attachmentHeader = SentryEnvelopeItemHeader('attachment', - contentType: 'image/png', fileName: 'sentry.png'); - final attachmentItem = SentryEnvelopeItem(attachmentHeader, dataFactory); - - // Envelope - - final eventId = SentryId.fromId('3b382f22ee67491f80f7dee18016a7b1'); - final sdkVersion = SdkVersion(name: 'test', version: 'version'); - final header = SentryEnvelopeHeader(eventId, sdkVersion); - final envelope = SentryEnvelope(header, [attachmentItem]); - - final envelopeData = []; - await envelope - .envelopeStream(defaultTestOptions()) - .forEach(envelopeData.addAll); - - final expectedEnvelopeFile = - File('test_resources/envelope-with-image.envelope'); - final expectedEnvelopeData = await expectedEnvelopeFile.readAsBytes(); - - expect(expectedEnvelopeData, envelopeData); - }); - - test('skips attachment if path is invalid', () async { - final event = SentryEvent( - eventId: SentryId.empty(), - timestamp: DateTime.utc(1970, 1, 1), - ); - final sdkVersion = SdkVersion(name: '', version: ''); - final attachment = - IoSentryAttachment.fromPath('this_path_does_not_exist.txt'); - final envelope = SentryEnvelope.fromEvent( - event, - sdkVersion, - dsn: fakeDsn, - attachments: [attachment], - ); - - final options = SentryOptions(dsn: testDsn) - ..automatedTestMode = - false; // We want to skip throwing envelope items in this test. - - final data = (await envelope.envelopeStream(options).toList()) - .reduce((a, b) => a + b); - - final file = File('test_resources/envelope-no-attachment.envelope'); - final jsonStr = await file.readAsString(); - final dataStr = utf8.decode(data); - - expect(dataStr, jsonStr); - }); - }); -} diff --git a/dart/test/sentry_event_test.dart b/dart/test/sentry_event_test.dart deleted file mode 100644 index 4a88a7aecb..0000000000 --- a/dart/test/sentry_event_test.dart +++ /dev/null @@ -1,500 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/platform/mock_platform.dart'; -import 'package:sentry/src/version.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; - -void main() { - group('deserialize', () { - final sentryId = SentryId.empty(); - final timestamp = DateTime.fromMillisecondsSinceEpoch(0); - final sentryEventJson = { - 'event_id': sentryId.toString(), - 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), - 'platform': 'platform', - 'logger': 'logger', - 'server_name': 'serverName', - 'release': 'release', - 'dist': 'dist', - 'environment': 'environment', - 'modules': {'key': 'value'}, - 'message': {'formatted': 'formatted'}, - 'transaction': 'transaction', - 'exception': { - 'values': [ - {'type': 'type', 'value': 'value'} - ] - }, - 'threads': { - 'values': [ - {'id': 0, 'crashed': true} - ] - }, - 'level': 'debug', - 'culprit': 'culprit', - 'tags': {'key': 'value'}, - 'extra': {'key': 'value'}, - 'contexts': { - 'device': {'name': 'name'} - }, - 'user': { - 'id': 'id', - 'username': 'username', - 'ip_address': '192.168.0.0.1' - }, - 'fingerprint': ['fingerprint'], - 'breadcrumbs': [ - { - 'message': 'message', - 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), - 'level': 'info' - } - ], - 'sdk': {'name': 'name', 'version': 'version'}, - 'request': {'url': 'url'}, - 'debug_meta': { - 'sdk_info': {'sdk_name': 'sdkName'} - }, - 'type': 'type', - }; - sentryEventJson.addAll(testUnknown); - - final emptyFieldsSentryEventJson = { - 'event_id': sentryId.toString(), - 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), - 'contexts': { - 'device': {'name': 'name'} - }, - }; - - test('fromJson', () { - final sentryEvent = SentryEvent.fromJson(sentryEventJson); - final json = sentryEvent.toJson(); - - expect( - DeepCollectionEquality().equals(sentryEventJson, json), - true, - ); - }); - - test('should not deserialize null or empty fields', () { - final sentryEvent = SentryEvent.fromJson(emptyFieldsSentryEventJson); - - expect(sentryEvent.platform, isNull); - expect(sentryEvent.logger, isNull); - expect(sentryEvent.serverName, isNull); - expect(sentryEvent.release, isNull); - expect(sentryEvent.dist, isNull); - expect(sentryEvent.environment, isNull); - expect(sentryEvent.modules, isNull); - expect(sentryEvent.message, isNull); - expect(sentryEvent.threads?.first.stacktrace, isNull); - expect(sentryEvent.exceptions?.first, isNull); - expect(sentryEvent.transaction, isNull); - expect(sentryEvent.level, isNull); - expect(sentryEvent.culprit, isNull); - expect(sentryEvent.tags, isNull); - // ignore: deprecated_member_use_from_same_package - expect(sentryEvent.extra, isNull); - expect(sentryEvent.breadcrumbs, isNull); - expect(sentryEvent.user, isNull); - expect(sentryEvent.fingerprint, isNull); - expect(sentryEvent.sdk, isNull); - expect(sentryEvent.request, isNull); - expect(sentryEvent.debugMeta, isNull); - expect(sentryEvent.type, isNull); - expect(sentryEvent.unknown, isNull); - }); - }); - - group(SentryEvent, () { - test('$Breadcrumb serializes', () { - expect( - Breadcrumb( - message: 'example log', - timestamp: DateTime.utc(2019), - level: SentryLevel.debug, - category: 'test', - ).toJson(), - { - 'timestamp': '2019-01-01T00:00:00.000Z', - 'message': 'example log', - 'category': 'test', - 'level': 'debug', - }, - ); - }); - test('$SdkVersion serializes', () { - final platform = MockPlatform(); - - final event = SentryEvent( - eventId: SentryId.empty(), - timestamp: DateTime.utc(2019), - platform: sdkPlatform(platform.isWeb), - sdk: SdkVersion( - name: 'sentry.dart.flutter', - version: '4.3.2', - integrations: ['integration'], - packages: [ - SentryPackage('npm:@sentry/javascript', '1.3.4'), - ], - ), - ); - expect(event.toJson(), { - 'platform': platform.isWeb ? 'javascript' : 'other', - 'event_id': '00000000000000000000000000000000', - 'timestamp': '2019-01-01T00:00:00.000Z', - 'sdk': { - 'name': 'sentry.dart.flutter', - 'version': '4.3.2', - 'packages': [ - {'name': 'npm:@sentry/javascript', 'version': '1.3.4'} - ], - 'integrations': ['integration'], - }, - }); - }); - test('serializes to JSON', () { - final platform = MockPlatform(); - - final timestamp = DateTime.utc(2019); - final user = SentryUser( - id: 'user_id', - username: 'username', - email: 'email@email.com', - ipAddress: '127.0.0.1', - data: const {'foo': 'bar'}, - ); - - final breadcrumbs = [ - Breadcrumb( - message: 'test log', - timestamp: timestamp, - level: SentryLevel.debug, - category: 'test', - ), - ]; - - final request = SentryRequest( - url: 'https://api.com/users', - method: 'GET', - headers: const {'authorization': '123456'}, - ); - - expect( - SentryEvent( - eventId: SentryId.empty(), - timestamp: timestamp, - platform: sdkPlatform(platform.isWeb), - message: SentryMessage( - 'test-message 1 2', - template: 'test-message %d %d', - params: ['1', '2'], - ), - transaction: '/test/1', - level: SentryLevel.debug, - culprit: 'Professor Moriarty', - tags: const { - 'a': 'b', - 'c': 'd', - }, - // ignore: deprecated_member_use_from_same_package - extra: const { - 'e': 'f', - 'g': 2, - }, - fingerprint: const [ - SentryEvent.defaultFingerprint, - 'foo' - ], - user: user, - breadcrumbs: breadcrumbs, - request: request, - debugMeta: DebugMeta( - sdk: SdkInfo( - sdkName: 'sentry.dart', - versionMajor: 4, - versionMinor: 1, - versionPatchlevel: 2, - ), - images: [ - DebugImage( - type: 'macho', - debugId: '84a04d24-0e60-3810-a8c0-90a65e2df61a', - debugFile: 'libDiagnosticMessagesClient.dylib', - codeFile: '/usr/lib/libDiagnosticMessagesClient.dylib', - imageAddr: '0x7fffe668e000', - imageSize: 8192, - arch: 'x86_64', - codeId: '123', - ) - ], - ), - type: 'type', - unknown: testUnknown) - .toJson(), - { - 'platform': platform.isWeb ? 'javascript' : 'other', - 'event_id': '00000000000000000000000000000000', - 'timestamp': '2019-01-01T00:00:00.000Z', - 'message': { - 'formatted': 'test-message 1 2', - 'message': 'test-message %d %d', - 'params': ['1', '2'] - }, - 'transaction': '/test/1', - 'level': 'debug', - 'culprit': 'Professor Moriarty', - 'tags': {'a': 'b', 'c': 'd'}, - 'extra': {'e': 'f', 'g': 2}, - 'fingerprint': ['{{ default }}', 'foo'], - 'user': { - 'id': 'user_id', - 'username': 'username', - 'email': 'email@email.com', - 'ip_address': '127.0.0.1', - 'data': {'foo': 'bar'} - }, - 'breadcrumbs': { - { - 'timestamp': '2019-01-01T00:00:00.000Z', - 'message': 'test log', - 'category': 'test', - 'level': 'debug', - }, - }, - 'request': { - 'url': request.url, - 'method': request.method, - 'headers': {'authorization': '123456'} - }, - 'debug_meta': { - 'sdk_info': { - 'sdk_name': 'sentry.dart', - 'version_major': 4, - 'version_minor': 1, - 'version_patchlevel': 2 - }, - 'images': [ - { - 'type': 'macho', - 'debug_id': '84a04d24-0e60-3810-a8c0-90a65e2df61a', - 'debug_file': 'libDiagnosticMessagesClient.dylib', - 'code_file': '/usr/lib/libDiagnosticMessagesClient.dylib', - 'image_addr': '0x7fffe668e000', - 'image_size': 8192, - 'arch': 'x86_64', - 'code_id': '123', - }, - ] - }, - 'type': 'type', - }..addAll(testUnknown), - ); - }); - - test('should not serialize throwable', () { - final error = StateError('test-error'); - - final serialized = SentryEvent(throwable: error).toJson(); - expect(serialized['throwable'], null); - expect(serialized['stacktrace'], null); - expect(serialized['exception'], null); - }); - - test('should serialize $SentryThread when no $SentryException present', () { - final serialized = SentryEvent(threads: [ - SentryThread( - id: 0, - crashed: true, - current: true, - name: 'Isolate', - ) - ]).toJson(); - expect(serialized['threads']['values'], isNotNull); - }); - - test('should serialize $SentryThread when id matches exception id', () { - final serialized = SentryEvent( - exceptions: [ - SentryException( - type: 'foo', - value: 'bar', - threadId: 0, - ) - ], - threads: [ - SentryThread( - id: 0, - crashed: true, - current: true, - name: 'Isolate', - ) - ], - ).toJson(); - expect(serialized['threads']?['values'], isNotEmpty); - }); - - test( - 'should not serialize event.threads.stacktrace ' - 'if event.exception is set', () { - // https://develop.sentry.dev/sdk/event-payloads/stacktrace/ - final stacktrace = - SentryStackTrace(frames: [SentryStackFrame(function: 'main')]); - final serialized = SentryEvent( - exceptions: [ - SentryException( - value: 'Bad state', - type: 'StateError', - threadId: 0, - stackTrace: stacktrace, - ) - ], - threads: [ - SentryThread( - crashed: true, - current: true, - id: 0, - name: 'Current isolate', - stacktrace: stacktrace, - ) - ], - ).toJson(); - - expect(serialized['threads']?['values']?.first['stacktrace'], isNull); - expect(serialized['threads']?['values']?.first['crashed'], true); - expect(serialized['threads']?['values']?.first['current'], true); - expect(serialized['threads']?['values']?.first['id'], 0); - expect( - serialized['threads']?['values']?.first['name'], - 'Current isolate', - ); - }); - - test( - 'should serialize event.threads.stacktrace ' - 'if event.exception.threadId does not match', () { - // https://develop.sentry.dev/sdk/event-payloads/stacktrace/ - final stacktrace = - SentryStackTrace(frames: [SentryStackFrame(function: 'main')]); - final serialized = SentryEvent( - exceptions: [ - SentryException( - value: 'Bad state', - type: 'StateError', - threadId: 1, - stackTrace: stacktrace, - ) - ], - threads: [ - SentryThread( - crashed: true, - current: true, - id: 0, - name: 'Current isolate', - stacktrace: stacktrace, - ) - ], - ).toJson(); - expect(serialized['threads']?['values'], isNotEmpty); - }); - - test('serializes to JSON with sentryException', () { - SentryException? sentryException; - try { - throw StateError('an error'); - } catch (err) { - sentryException = SentryException( - type: '${err.runtimeType}', - value: '$err', - mechanism: Mechanism( - type: 'mech-type', - description: 'a description', - helpLink: 'https://help.com', - synthetic: false, - handled: true, - meta: {}, - data: {}, - ), - ); - } - - final serialized = SentryEvent(exceptions: [sentryException]).toJson(); - - expect(serialized['exception']['values'].first['type'], 'StateError'); - expect( - serialized['exception']['values'].first['value'], - 'Bad state: an error', - ); - expect( - serialized['exception']['values'].first['mechanism'], - { - 'type': 'mech-type', - 'description': 'a description', - 'help_link': 'https://help.com', - 'synthetic': false, - 'handled': true, - }, - ); - }); - - test('should not serialize null or empty fields', () { - final event = SentryEvent( - message: null, - modules: {}, - exceptions: [SentryException(type: null, value: null)], - threads: [SentryThread(stacktrace: SentryStackTrace(frames: []))], - tags: {}, - // ignore: deprecated_member_use_from_same_package - extra: {}, - contexts: Contexts(), - fingerprint: [], - breadcrumbs: [Breadcrumb()], - request: SentryRequest(), - debugMeta: DebugMeta(images: []), - type: null, - ); - final eventMap = event.toJson(); - - expect(eventMap['message'], isNull); - expect(eventMap['modules'], isNull); - expect(eventMap['exception'], isNull); - expect(eventMap['stacktrace'], isNull); - expect(eventMap['tags'], isNull); - expect(eventMap['extra'], isNull); - expect(eventMap['contexts'], isNull); - expect(eventMap['fingerprint'], isNull); - expect(eventMap['request'], isNull); - expect(eventMap['debug_meta'], isNull); - expect(eventMap['type'], isNull); - }); - - test( - 'throwable and throwableMechanism should return the error if no mechanism', - () { - final error = StateError('test-error'); - final event = SentryEvent(throwable: error); - - expect(event.throwable, error); - expect(event.throwableMechanism, error); - }); - - test( - 'throwableMechanism getter should return the ThrowableMechanism if theres a mechanism', - () { - final error = StateError('test-error'); - final mechanism = Mechanism(type: 'FlutterError', handled: true); - final throwableMechanism = ThrowableMechanism(mechanism, error); - final event = SentryEvent(throwable: throwableMechanism); - - expect(event.throwable, error); - expect(event.throwableMechanism, throwableMechanism); - }); - }); -} diff --git a/dart/test/sentry_exception_factory_test.dart b/dart/test/sentry_exception_factory_test.dart deleted file mode 100644 index ad6df2a724..0000000000 --- a/dart/test/sentry_exception_factory_test.dart +++ /dev/null @@ -1,355 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/sentry_exception_factory.dart'; -import 'package:test/test.dart'; - -import 'test_utils.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('getSentryException with frames', () { - SentryException sentryException; - try { - throw StateError('a state error'); - } catch (err, stacktrace) { - sentryException = fixture.getSut().getSentryException( - err, - stackTrace: stacktrace, - ); - } - - expect(sentryException.type, 'StateError'); - expect(sentryException.stackTrace!.frames, isNotEmpty); - }); - - test('getSentryException without frames', () { - SentryException sentryException; - try { - throw StateError('a state error'); - } catch (err, _) { - sentryException = fixture.getSut().getSentryException( - err, - stackTrace: '', - ); - } - - expect(sentryException.type, 'StateError'); - expect(sentryException.stackTrace, isNull); - }); - - test('getSentryException without frames', () { - SentryException sentryException; - try { - throw StateError('a state error'); - } catch (err, _) { - sentryException = fixture.getSut().getSentryException( - err, - stackTrace: '', - ); - } - - expect(sentryException.type, 'StateError'); - expect(sentryException.stackTrace, isNull); - }); - - test('should not override event.stacktrace', () { - SentryException sentryException; - try { - throw StateError('a state error'); - } catch (err, _) { - sentryException = fixture.getSut().getSentryException( - err, - stackTrace: ''' -#0 baz (file:///pathto/test.dart:50:3) - -#1 bar (file:///pathto/test.dart:46:9) - ''', - ); - } - - expect(sentryException.type, 'StateError'); - expect(sentryException.stackTrace!.frames.first.lineNo, 46); - expect(sentryException.stackTrace!.frames.first.colNo, 9); - expect(sentryException.stackTrace!.frames.first.fileName, 'test.dart'); - }); - - test('should extract stackTrace from custom exception', () { - fixture.options - .addExceptionStackTraceExtractor(CustomExceptionStackTraceExtractor()); - - SentryException sentryException; - try { - throw CustomException(StackTrace.fromString(''' -#0 baz (file:///pathto/test.dart:50:3) - -#1 bar (file:///pathto/test.dart:46:9) - ''')); - } catch (err, _) { - sentryException = fixture.getSut().getSentryException( - err, - ); - } - - expect(sentryException.type, 'CustomException'); - expect(sentryException.stackTrace!.frames.first.lineNo, 46); - expect(sentryException.stackTrace!.frames.first.colNo, 9); - expect(sentryException.stackTrace!.frames.first.fileName, 'test.dart'); - }); - - test('should not fail when stackTrace property does not exist', () { - SentryException sentryException; - try { - throw Object(); - } catch (err, _) { - sentryException = fixture.getSut().getSentryException( - err, - ); - } - - expect(sentryException.type, 'Object'); - expect(sentryException.stackTrace, isNotNull); - }); - - test('getSentryException with not thrown Error and frames', () { - final sentryException = fixture.getSut().getSentryException( - CustomError(), - ); - - expect(sentryException.type, 'CustomError'); - expect(sentryException.stackTrace?.frames, isNotEmpty); - - // skip on browser because [StackTrace.current] still returns null - }, onPlatform: {'browser': Skip()}); - - test('getSentryException with not thrown Error and empty frames', () { - final sentryException = fixture - .getSut() - .getSentryException(CustomError(), stackTrace: StackTrace.empty); - - expect(sentryException.type, 'CustomError'); - expect(sentryException.stackTrace?.frames, isNotEmpty); - - // skip on browser because [StackTrace.current] still returns null - }, onPlatform: {'browser': Skip()}); - - test('reads the snapshot from the mechanism', () { - final error = StateError('test-error'); - final mechanism = Mechanism(type: 'Mechanism'); - final throwableMechanism = ThrowableMechanism( - mechanism, - error, - snapshot: true, - ); - - SentryException sentryException; - try { - throw throwableMechanism; - } catch (err, stackTrace) { - sentryException = fixture.getSut().getSentryException( - throwableMechanism, - stackTrace: stackTrace, - ); - } - - expect(sentryException.stackTrace!.snapshot, true); - }); - - test('getSentryException adds throwable', () { - SentryException sentryException; - dynamic throwable; - try { - throw StateError('a state error'); - } catch (err, stacktrace) { - throwable = err; - sentryException = fixture.getSut().getSentryException( - err, - stackTrace: stacktrace, - ); - } - - expect(sentryException.throwable, throwable); - }); - - test('should remove stackTrace string from value', () { - final stackTraceError = StackTraceError(); - final sentryException = fixture.getSut().getSentryException(stackTraceError, - stackTrace: StackTraceErrorStackTrace()); - final expected = - "NetworkError(type: NetworkErrorType.unknown, error: Instance of 'iH')"; - - expect(sentryException.value, expected); - }); - - test('no empty value', () { - final stackTraceError = StackTraceError(); - stackTraceError.prefix = ""; - final sentryException = fixture.getSut().getSentryException(stackTraceError, - stackTrace: StackTraceErrorStackTrace()); - - expect(sentryException.value, isNull); - }); - - test( - 'set snapshot to true when no stracktrace is present & attachStacktrace == true', - () { - final sentryException = - fixture.getSut(attachStacktrace: true).getSentryException(Object()); - - expect(sentryException.stackTrace!.snapshot, true); - }); - - test( - 'set snapshot to false when no stracktrace is present & attachStacktrace == false', - () { - final sentryException = - fixture.getSut(attachStacktrace: false).getSentryException(Object()); - - // stackTrace is null anyway when not present and attachStacktrace false - expect(sentryException.stackTrace?.snapshot, isNull); - }); - - test('sets stacktrace build id and image address', () { - final sentryException = fixture - .getSut(attachStacktrace: false) - .getSentryException(Object(), stackTrace: StackTraceErrorStackTrace()); - - final sentryStackTrace = sentryException.stackTrace!; - expect(sentryStackTrace.baseAddr, '0x752602b000'); - expect(sentryStackTrace.buildId, 'bca64abfdfcc84d231bb8f1ccdbfbd8d'); - }); - - test('sets null build id and image address if not present', () { - final sentryException = fixture - .getSut(attachStacktrace: false) - .getSentryException(Object(), stackTrace: null); - - // stackTrace is null anyway with null stack trace and attachStacktrace false - final sentryStackTrace = sentryException.stackTrace; - expect(sentryStackTrace?.baseAddr, isNull); - expect(sentryStackTrace?.buildId, isNull); - }); - - test('remove sentry frames', () { - final sentryException = - fixture.getSut(attachStacktrace: false).getSentryException( - SentryStackTraceError(), - stackTrace: SentryStackTrace(), - removeSentryFrames: true, - ); - - final sentryStackTrace = sentryException.stackTrace!; - expect(sentryStackTrace.baseAddr, isNull); - - expect(sentryStackTrace.frames.length, 17); - expect(sentryStackTrace.frames[16].package, 'sentry_flutter_example'); - expect(sentryStackTrace.frames[15].package, 'flutter'); - }); -} - -class CustomError extends Error {} - -class CustomException implements Exception { - final StackTrace stackTrace; - - CustomException(this.stackTrace); -} - -class CustomExceptionStackTraceExtractor - extends ExceptionStackTraceExtractor { - @override - StackTrace? stackTrace(CustomException error) { - return error.stackTrace; - } -} - -class StackTraceError extends Error { - var prefix = - "NetworkError(type: NetworkErrorType.unknown, error: Instance of 'iH')"; - - @override - String toString() { - return ''' -$prefix - -${StackTraceErrorStackTrace()}'''; - } -} - -class StackTraceErrorStackTrace implements StackTrace { - @override - String toString() { - return ''' -pid: 9437, tid: 10069, name 1.ui -os: android arch: arm64 comp: yes sim: no -build_id: 'bca64abfdfcc84d231bb8f1ccdbfbd8d' -isolate_dso_base: 752602b000, vm_dso_base: 752602b000 -isolate_instructions: 7526344980, vm_instructions: 752633f000 -#00 abs 00000075266c2fbf virt 0000000000697fbf _kDartIsolateSnapshotInstructions+0x37e63f -#1 abs 000000752685211f virt 000000000082711f _kDartIsolateSnapshotInstructions+0x50d79f -#2 abs 0000007526851cb3 virt 0000000000826cb3 _kDartIsolateSnapshotInstructions+0x50d333 -#3 abs 0000007526851c63 virt 0000000000826c63 _kDartIsolateSnapshotInstructions+0x50d2e3 -#4 abs 0000007526851bf3 virt 0000000000826bf3 _kDartIsolateSnapshotInstructions+0x50d273 -#5 abs 0000007526a0b44b virt 00000000009e044b _kDartIsolateSnapshotInstructions+0x6c6acb -#6 abs 0000007526a068a7 virt 00000000009db8a7 _kDartIsolateSnapshotInstructions+0x6c1f27 -#7 abs 0000007526b57a2b virt 0000000000b2ca2b _kDartIsolateSnapshotInstructions+0x8130ab -#8 abs 0000007526b5d93b virt 0000000000b3293b _kDartIsolateSnapshotInstructions+0x818fbb -#9 abs 0000007526a2333b virt 00000000009f833b _kDartIsolateSnapshotInstructions+0x6de9bb -#10 abs 0000007526937957 virt 000000000090c957 _kDartIsolateSnapshotInstructions+0x5f2fd7 -#11 abs 0000007526a243a3 virt 00000000009f93a3 _kDartIsolateSnapshotInstructions+0x6dfa23 -#12 abs 000000752636273b virt 000000000033773b _kDartIsolateSnapshotInstructions+0x1ddbb -#13 abs 0000007526a36ac3 virt 0000000000a0bac3 _kDartIsolateSnapshotInstructions+0x6f2143 -#14 abs 00000075263626af virt 00000000003376af _kDartIsolateSnapshotInstructions+0x1dd2f'''; - } -} - -class SentryStackTraceError extends Error { - var prefix = "Unknown error without own stacktrace"; - - @override - String toString() { - return ''' -$prefix - -${SentryStackTrace()}'''; - } -} - -class SentryStackTrace implements StackTrace { - @override - String toString() { - return ''' - #0 getCurrentStackTrace (package:sentry/src/utils/stacktrace_utils.dart:10:49) -#1 OnErrorIntegration.call. (package:sentry_flutter/src/integrations/on_error_integration.dart:82:22) -#2 MainScaffold.build. (package:sentry_flutter_example/main.dart:349:23) -#3 _InkResponseState.handleTap (package:flutter/src/material/ink_well.dart:1170:21) -#4 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:351:24) -#5 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:656:11) -#6 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:313:5) -#7 BaseTapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:283:7) -#8 GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:169:27) -#9 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:505:20) -#10 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:481:22) -#11 RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:450:11) -#12 GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:426:7) -#13 GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:389:5) -#14 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:336:7) -#15 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:305:9) -#16 _invoke1 (dart:ui/hooks.dart:328:13) -#17 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:442:7) -#18 _dispatchPointerDataPacket (dart:ui/hooks.dart:262:31) - '''; - } -} - -class Fixture { - final options = defaultTestOptions(); - - SentryExceptionFactory getSut({bool attachStacktrace = true}) { - options.attachStacktrace = attachStacktrace; - return SentryExceptionFactory(options); - } -} diff --git a/dart/test/sentry_id_test.dart b/dart/test/sentry_id_test.dart deleted file mode 100644 index 23878f7082..0000000000 --- a/dart/test/sentry_id_test.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:sentry/src/protocol/sentry_id.dart'; -import 'package:test/test.dart'; - -void main() { - test('empty id', () { - expect(SentryId.empty().toString(), '00000000000000000000000000000000'); - }); - - test('empty id equals from empty id', () { - expect( - SentryId.empty(), - SentryId.fromId('00000000000000000000000000000000'), - ); - }); - - test('uuid format with dashes', () { - expect( - SentryId.fromId('00000000-0000-0000-0000-000000000000'), - SentryId.empty(), - ); - }); - - test('empty id equality', () { - expect(SentryId.empty(), SentryId.empty()); - }); - - test('id roundtrip', () { - final id = SentryId.newId(); - expect(id, SentryId.fromId(id.toString())); - }); - - test('newId should not be equal to newId', () { - expect(SentryId.newId() == SentryId.newId(), false); - }); -} diff --git a/dart/test/sentry_io_client_test.dart b/dart/test/sentry_io_client_test.dart deleted file mode 100644 index a775073b7c..0000000000 --- a/dart/test/sentry_io_client_test.dart +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -@TestOn('vm') -library; - -import 'dart:io'; - -import 'package:test/test.dart'; - -import 'test_utils.dart'; - -void main() { - runTest(gzip: gzip); -} diff --git a/dart/test/sentry_isolate_extension_test.dart b/dart/test/sentry_isolate_extension_test.dart deleted file mode 100644 index 8cffea9c1c..0000000000 --- a/dart/test/sentry_isolate_extension_test.dart +++ /dev/null @@ -1,60 +0,0 @@ -@TestOn('vm') -library; - -import 'dart:isolate'; - -import 'package:sentry/src/sentry_isolate_extension.dart'; -import 'package:test/test.dart'; - -import 'mocks/mock_hub.dart'; -import 'test_utils.dart'; - -void main() { - group("SentryIsolate", () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('add error listener', () async { - final throwingClosure = (String message) async { - throw StateError(message); - }; - - final isolate = - await Isolate.spawn(throwingClosure, "message", paused: true); - isolate.addSentryErrorListener(hub: fixture.hub); - isolate.resume(isolate.pauseCapability!); - - await Future.delayed(Duration(milliseconds: 10)); - - expect(fixture.hub.captureEventCalls.first, isNotNull); - }); - - test('remove error listener', () async { - final throwingClosure = (String message) async { - throw StateError(message); - }; - - final isolate = - await Isolate.spawn(throwingClosure, "message", paused: true); - final port = isolate.addSentryErrorListener(hub: fixture.hub); - isolate.removeSentryErrorListener(port); - isolate.resume(isolate.pauseCapability!); - - await Future.delayed(Duration(milliseconds: 10)); - - expect(fixture.hub.captureEventCalls.isEmpty, true); - }); - }); -} - -class Fixture { - final hub = MockHub(); - final options = defaultTestOptions()..tracesSampleRate = 1.0; - - Isolate getSut() { - return Isolate.current; - } -} diff --git a/dart/test/sentry_isolate_test.dart b/dart/test/sentry_isolate_test.dart deleted file mode 100644 index 597c1de795..0000000000 --- a/dart/test/sentry_isolate_test.dart +++ /dev/null @@ -1,75 +0,0 @@ -@TestOn('vm') -library; - -import 'package:sentry/src/hub.dart'; -import 'package:sentry/src/protocol/sentry_level.dart'; -import 'package:sentry/src/protocol/span_status.dart'; -import 'package:sentry/src/sentry_isolate.dart'; -import 'package:test/test.dart'; - -import 'mocks/mock_hub.dart'; -import 'mocks/mock_sentry_client.dart'; -import 'test_utils.dart'; - -void main() { - group("SentryIsolate", () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('adds error listener', () async { - final throwingClosure = (String message) async { - throw StateError(message); - }; - - await SentryIsolate.spawn(throwingClosure, "message", hub: fixture.hub); - await Future.delayed(Duration(milliseconds: 10)); - - expect(fixture.hub.captureEventCalls.first, isNotNull); - }); - - test('marks transaction as internal error if no status', () async { - final exception = StateError('error'); - final stackTrace = StackTrace.current.toString(); - - final hub = Hub(fixture.options); - final client = MockSentryClient(); - hub.bindClient(client); - - hub.startTransaction('name', 'operation', bindToScope: true); - - await SentryIsolate.handleIsolateError( - hub, [exception.toString(), stackTrace]); - - final span = hub.getSpan(); - - expect(span?.status, const SpanStatus.internalError()); - - await span?.finish(); - }); - - test('sets level to error instead of fatal', () async { - final exception = StateError('error'); - final stackTrace = StackTrace.current.toString(); - - final hub = Hub(fixture.options); - final client = MockSentryClient(); - hub.bindClient(client); - - fixture.options.markAutomaticallyCollectedErrorsAsFatal = false; - - await SentryIsolate.handleIsolateError( - hub, [exception.toString(), stackTrace]); - - final capturedEvent = client.captureEventCalls.last.event; - expect(capturedEvent.level, SentryLevel.error); - }); - }); -} - -class Fixture { - final hub = MockHub(); - final options = defaultTestOptions()..tracesSampleRate = 1.0; -} diff --git a/dart/test/sentry_log_batcher_test.dart b/dart/test/sentry_log_batcher_test.dart deleted file mode 100644 index 5e9f7cf5f3..0000000000 --- a/dart/test/sentry_log_batcher_test.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:test/test.dart'; -import 'package:sentry/src/sentry_log_batcher.dart'; -import 'package:sentry/src/sentry_options.dart'; -import 'package:sentry/src/protocol/sentry_log.dart'; -import 'package:sentry/src/protocol/sentry_log_level.dart'; - -import 'mocks/mock_transport.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('added logs are flushed after timeout', () async { - final flushTimeout = Duration(milliseconds: 1); - - final batcher = fixture.getSut(flushTimeout: flushTimeout); - - final log = SentryLog( - timestamp: DateTime.now(), - level: SentryLogLevel.info, - body: 'test', - attributes: {}, - ); - final log2 = SentryLog( - timestamp: DateTime.now(), - level: SentryLogLevel.info, - body: 'test2', - attributes: {}, - ); - - batcher.addLog(log); - batcher.addLog(log2); - - expect(fixture.mockTransport.envelopes.length, 0); - - await Future.delayed(flushTimeout); - - expect(fixture.mockTransport.envelopes.length, 1); - - final envelopePayloadJson = (fixture.mockTransport).logs.first; - - expect(envelopePayloadJson, isNotNull); - expect(envelopePayloadJson['items'].length, 2); - expect(envelopePayloadJson['items'].first['body'], log.body); - expect(envelopePayloadJson['items'].last['body'], log2.body); - }); - - test('max logs are flushed without timeout', () async { - final batcher = fixture.getSut(maxBufferSize: 10); - - final log = SentryLog( - timestamp: DateTime.now(), - level: SentryLogLevel.info, - body: 'test', - attributes: {}, - ); - - for (var i = 0; i < 10; i++) { - batcher.addLog(log); - } - - // Just wait a little bit, as we call capture without awaiting internally. - await Future.delayed(Duration(milliseconds: 1)); - - expect(fixture.mockTransport.envelopes.length, 1); - final envelopePayloadJson = (fixture.mockTransport).logs.first; - - expect(envelopePayloadJson, isNotNull); - expect(envelopePayloadJson['items'].length, 10); - }); - - test('more than max logs are flushed eventuelly', () async { - final flushTimeout = Duration(milliseconds: 100); - final batcher = fixture.getSut( - maxBufferSize: 10, - flushTimeout: flushTimeout, - ); - - final log = SentryLog( - timestamp: DateTime.now(), - level: SentryLogLevel.info, - body: 'test', - attributes: {}, - ); - - for (var i = 0; i < 15; i++) { - batcher.addLog(log); - } - - await Future.delayed(flushTimeout); - - expect(fixture.mockTransport.envelopes.length, 2); - - final firstEnvelopePayloadJson = (fixture.mockTransport).logs.first; - - expect(firstEnvelopePayloadJson, isNotNull); - expect(firstEnvelopePayloadJson['items'].length, 10); - - final secondEnvelopePayloadJson = (fixture.mockTransport).logs.last; - - expect(secondEnvelopePayloadJson, isNotNull); - expect(secondEnvelopePayloadJson['items'].length, 5); - }); - - test('calling flush directly flushes logs', () async { - final batcher = fixture.getSut(); - - final log = SentryLog( - timestamp: DateTime.now(), - level: SentryLogLevel.info, - body: 'test', - attributes: {}, - ); - - batcher.addLog(log); - batcher.addLog(log); - batcher.flush(); - - // Just wait a little bit, as we call capture without awaiting internally. - await Future.delayed(Duration(milliseconds: 1)); - - expect(fixture.mockTransport.envelopes.length, 1); - final envelopePayloadJson = (fixture.mockTransport).logs.first; - - expect(envelopePayloadJson, isNotNull); - expect(envelopePayloadJson['items'].length, 2); - }); -} - -class Fixture { - final options = SentryOptions(); - final mockTransport = MockTransport(); - - Fixture() { - options.transport = mockTransport; - } - - SentryLogBatcher getSut({Duration? flushTimeout, int? maxBufferSize}) { - return SentryLogBatcher( - options, - flushTimeout: flushTimeout, - maxBufferSize: maxBufferSize, - ); - } -} diff --git a/dart/test/sentry_logger_formatter_test.dart b/dart/test/sentry_logger_formatter_test.dart deleted file mode 100644 index 946eee19f2..0000000000 --- a/dart/test/sentry_logger_formatter_test.dart +++ /dev/null @@ -1,307 +0,0 @@ -import 'package:test/test.dart'; -import 'package:sentry/src/sentry_logger_formatter.dart'; -import 'package:sentry/src/sentry_logger.dart'; -import 'package:sentry/src/protocol/sentry_log_attribute.dart'; - -void main() { - final fixture = Fixture(); - - void verifyPassedAttributes(Map attributes) { - expect(attributes['foo'].type, 'string'); - expect(attributes['foo'].value, 'bar'); - } - - void verifyBasicTemplate(String body, Map attributes) { - expect(body, 'Hello, World!'); - expect(attributes['sentry.message.template'].type, 'string'); - expect(attributes['sentry.message.template'].value, 'Hello, %s!'); - expect(attributes['sentry.message.parameter.0'].type, 'string'); - expect(attributes['sentry.message.parameter.0'].value, 'World'); - verifyPassedAttributes(attributes); - } - - void verifyTemplateWithMultipleArguments( - String body, Map attributes) { - expect(body, 'Name: Alice, Age: 30, Active: true, Score: 95.5'); - expect(attributes['sentry.message.template'].type, 'string'); - expect(attributes['sentry.message.template'].value, - 'Name: %s, Age: %s, Active: %s, Score: %s'); - expect(attributes['sentry.message.parameter.0'].type, 'string'); - expect(attributes['sentry.message.parameter.0'].value, 'Alice'); - expect(attributes['sentry.message.parameter.1'].type, 'integer'); - expect(attributes['sentry.message.parameter.1'].value, 30); - expect(attributes['sentry.message.parameter.2'].type, 'boolean'); - expect(attributes['sentry.message.parameter.2'].value, true); - expect(attributes['sentry.message.parameter.3'].type, 'double'); - expect(attributes['sentry.message.parameter.3'].value, 95.5); - verifyPassedAttributes(attributes); - } - - group('format basic template', () { - test('for trace', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.trace( - "Hello, %s!", - ["World"], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.traceCalls.length, 1); - final message = logger.traceCalls[0].message; - final attributes = logger.traceCalls[0].attributes!; - verifyBasicTemplate(message, attributes); - }); - - test('for debug', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.debug( - "Hello, %s!", - ["World"], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.debugCalls.length, 1); - final message = logger.debugCalls[0].message; - final attributes = logger.debugCalls[0].attributes!; - verifyBasicTemplate(message, attributes); - }); - - test('for info', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.info( - "Hello, %s!", - ["World"], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.infoCalls.length, 1); - final message = logger.infoCalls[0].message; - final attributes = logger.infoCalls[0].attributes!; - verifyBasicTemplate(message, attributes); - }); - - test('for warn', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.warn( - "Hello, %s!", - ["World"], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.warnCalls.length, 1); - final message = logger.warnCalls[0].message; - final attributes = logger.warnCalls[0].attributes!; - verifyBasicTemplate(message, attributes); - }); - - test('for error', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.error( - "Hello, %s!", - ["World"], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.errorCalls.length, 1); - final message = logger.errorCalls[0].message; - final attributes = logger.errorCalls[0].attributes!; - verifyBasicTemplate(message, attributes); - }); - - test('for fatal', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.fatal( - "Hello, %s!", - ["World"], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.fatalCalls.length, 1); - final message = logger.fatalCalls[0].message; - final attributes = logger.fatalCalls[0].attributes!; - verifyBasicTemplate(message, attributes); - }); - }); - - group('template with multiple arguments', () { - test('for trace', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.trace( - "Name: %s, Age: %s, Active: %s, Score: %s", - ['Alice', 30, true, 95.5], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.traceCalls.length, 1); - final message = logger.traceCalls[0].message; - final attributes = logger.traceCalls[0].attributes!; - verifyTemplateWithMultipleArguments(message, attributes); - }); - - test('for trace', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.trace( - "Name: %s, Age: %s, Active: %s, Score: %s", - ['Alice', 30, true, 95.5], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.traceCalls.length, 1); - final message = logger.traceCalls[0].message; - final attributes = logger.traceCalls[0].attributes!; - verifyTemplateWithMultipleArguments(message, attributes); - }); - - test('for debug', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.debug( - "Name: %s, Age: %s, Active: %s, Score: %s", - ['Alice', 30, true, 95.5], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.debugCalls.length, 1); - final message = logger.debugCalls[0].message; - final attributes = logger.debugCalls[0].attributes!; - verifyTemplateWithMultipleArguments(message, attributes); - }); - - test('for info', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.info( - "Name: %s, Age: %s, Active: %s, Score: %s", - ['Alice', 30, true, 95.5], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.infoCalls.length, 1); - final message = logger.infoCalls[0].message; - final attributes = logger.infoCalls[0].attributes!; - verifyTemplateWithMultipleArguments(message, attributes); - }); - - test('for warn', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.warn( - "Name: %s, Age: %s, Active: %s, Score: %s", - ['Alice', 30, true, 95.5], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.warnCalls.length, 1); - final message = logger.warnCalls[0].message; - final attributes = logger.warnCalls[0].attributes!; - verifyTemplateWithMultipleArguments(message, attributes); - }); - - test('for error', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.error( - "Name: %s, Age: %s, Active: %s, Score: %s", - ['Alice', 30, true, 95.5], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.errorCalls.length, 1); - final message = logger.errorCalls[0].message; - final attributes = logger.errorCalls[0].attributes!; - verifyTemplateWithMultipleArguments(message, attributes); - }); - - test('for fatal', () { - final logger = MockLogger(); - final sut = fixture.getSut(logger); - - sut.fatal( - "Name: %s, Age: %s, Active: %s, Score: %s", - ['Alice', 30, true, 95.5], - attributes: {'foo': SentryLogAttribute.string('bar')}, - ); - - expect(logger.fatalCalls.length, 1); - final message = logger.fatalCalls[0].message; - final attributes = logger.fatalCalls[0].attributes!; - verifyTemplateWithMultipleArguments(message, attributes); - }); - }); -} - -class Fixture { - SentryLoggerFormatter getSut(SentryLogger logger) { - return SentryLoggerFormatter(logger); - } -} - -class MockLogger implements SentryLogger { - var traceCalls = []; - var debugCalls = []; - var infoCalls = []; - var warnCalls = []; - var errorCalls = []; - var fatalCalls = []; - - @override - SentryLoggerFormatter get fmt => throw UnimplementedError(); - - @override - Future trace(String message, {Map? attributes}) async { - traceCalls.add((message: message, attributes: attributes)); - return; - } - - @override - Future debug(String message, {Map? attributes}) async { - debugCalls.add((message: message, attributes: attributes)); - return; - } - - @override - Future info(String message, {Map? attributes}) async { - infoCalls.add((message: message, attributes: attributes)); - return; - } - - @override - Future warn(String message, {Map? attributes}) async { - warnCalls.add((message: message, attributes: attributes)); - return; - } - - @override - Future error(String message, {Map? attributes}) async { - errorCalls.add((message: message, attributes: attributes)); - return; - } - - @override - Future fatal(String message, {Map? attributes}) async { - fatalCalls.add((message: message, attributes: attributes)); - return; - } -} - -typedef LoggerCall = ({String message, Map? attributes}); diff --git a/dart/test/sentry_logger_test.dart b/dart/test/sentry_logger_test.dart deleted file mode 100644 index 96e4aa7743..0000000000 --- a/dart/test/sentry_logger_test.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; -import 'test_utils.dart'; -import 'mocks/mock_hub.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - void verifyCaptureLog(SentryLogLevel level) { - expect(fixture.hub.captureLogCalls.length, 1); - final capturedLog = fixture.hub.captureLogCalls[0].log; - - expect(capturedLog.timestamp, fixture.timestamp); - expect(capturedLog.level, level); - expect(capturedLog.body, 'test'); - expect(capturedLog.attributes, fixture.attributes); - } - - test('sentry logger', () { - final logger = fixture.getSut(); - - logger.info('test', attributes: fixture.attributes); - - verifyCaptureLog(SentryLogLevel.info); - }); - - test('trace', () { - final logger = fixture.getSut(); - - logger.info('test', attributes: fixture.attributes); - - verifyCaptureLog(SentryLogLevel.info); - }); - - test('debug', () { - final logger = fixture.getSut(); - - logger.debug('test', attributes: fixture.attributes); - - verifyCaptureLog(SentryLogLevel.debug); - }); - - test('info', () { - final logger = fixture.getSut(); - - logger.info('test', attributes: fixture.attributes); - - verifyCaptureLog(SentryLogLevel.info); - }); - - test('warn', () { - final logger = fixture.getSut(); - - logger.warn('test', attributes: fixture.attributes); - - verifyCaptureLog(SentryLogLevel.warn); - }); - - test('error', () { - final logger = fixture.getSut(); - - logger.error('test', attributes: fixture.attributes); - - verifyCaptureLog(SentryLogLevel.error); - }); - - test('fatal', () { - final logger = fixture.getSut(); - - logger.fatal('test', attributes: fixture.attributes); - - verifyCaptureLog(SentryLogLevel.fatal); - }); -} - -class Fixture { - final options = defaultTestOptions(); - final hub = MockHub(); - final timestamp = DateTime.fromMicrosecondsSinceEpoch(0); - - final attributes = { - 'string': SentryLogAttribute.string('string'), - 'int': SentryLogAttribute.int(1), - 'double': SentryLogAttribute.double(1.0), - 'bool': SentryLogAttribute.bool(true), - }; - - SentryLogger getSut() { - return SentryLogger(() => timestamp, hub: hub); - } -} diff --git a/dart/test/sentry_measurement_test.dart b/dart/test/sentry_measurement_test.dart deleted file mode 100644 index 1dddee645a..0000000000 --- a/dart/test/sentry_measurement_test.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - group('$SentryMeasurement', () { - test('total frames has none unit', () { - expect( - SentryMeasurement.totalFrames(10).unit, SentryMeasurementUnit.none); - }); - - test('slow frames has none unit', () { - expect(SentryMeasurement.slowFrames(10).unit, SentryMeasurementUnit.none); - }); - - test('frozen frames has none unit', () { - expect( - SentryMeasurement.frozenFrames(10).unit, SentryMeasurementUnit.none); - }); - - test('warm start has milliseconds unit', () { - expect(SentryMeasurement.warmAppStart(Duration(seconds: 1)).unit, - DurationSentryMeasurementUnit.milliSecond); - }); - - test('cold start has milliseconds unit', () { - expect(SentryMeasurement.coldAppStart(Duration(seconds: 1)).unit, - DurationSentryMeasurementUnit.milliSecond); - }); - - test('toJson sets unit if given', () { - final measurement = SentryMeasurement('name', 10, - unit: DurationSentryMeasurementUnit.milliSecond); - final map = { - 'value': 10, - 'unit': 'millisecond', - }; - - expect(MapEquality().equals(measurement.toJson(), map), true); - }); - }); -} diff --git a/dart/test/sentry_measurement_unit_test.dart b/dart/test/sentry_measurement_unit_test.dart deleted file mode 100644 index e0ca1cb5b8..0000000000 --- a/dart/test/sentry_measurement_unit_test.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - group('$SentryMeasurementUnit', () { - group('DurationUnit', () { - test('nanosecond', () { - expect(DurationSentryMeasurementUnit.nanoSecond.toStringValue(), - 'nanosecond'); - }); - - test('microsecond', () { - expect(DurationSentryMeasurementUnit.microSecond.toStringValue(), - 'microsecond'); - }); - - test('millisecond', () { - expect(DurationSentryMeasurementUnit.milliSecond.toStringValue(), - 'millisecond'); - }); - - test('second', () { - expect(DurationSentryMeasurementUnit.second.toStringValue(), 'second'); - }); - - test('minute', () { - expect(DurationSentryMeasurementUnit.minute.toStringValue(), 'minute'); - }); - - test('hour', () { - expect(DurationSentryMeasurementUnit.hour.toStringValue(), 'hour'); - }); - - test('day', () { - expect(DurationSentryMeasurementUnit.day.toStringValue(), 'day'); - }); - - test('week', () { - expect(DurationSentryMeasurementUnit.week.toStringValue(), 'week'); - }); - }); - - group('FractionUnit', () { - test('ratio', () { - expect(FractionSentryMeasurementUnit.ratio.toStringValue(), 'ratio'); - }); - - test('percent', () { - expect( - FractionSentryMeasurementUnit.percent.toStringValue(), 'percent'); - }); - }); - - group('None', () { - test('none', () { - expect(SentryMeasurementUnit.none.toStringValue(), 'none'); - }); - }); - - group('InformationUnit', () { - test('bit', () { - expect(InformationSentryMeasurementUnit.bit.toStringValue(), 'bit'); - }); - - test('byte', () { - expect(InformationSentryMeasurementUnit.byte.toStringValue(), 'byte'); - }); - - test('kilobyte', () { - expect(InformationSentryMeasurementUnit.kiloByte.toStringValue(), - 'kilobyte'); - }); - - test('kibibyte', () { - expect(InformationSentryMeasurementUnit.kibiByte.toStringValue(), - 'kibibyte'); - }); - - test('megabyte', () { - expect(InformationSentryMeasurementUnit.megaByte.toStringValue(), - 'megabyte'); - }); - - test('mebibyte', () { - expect(InformationSentryMeasurementUnit.mebiByte.toStringValue(), - 'mebibyte'); - }); - - test('gigabyte', () { - expect(InformationSentryMeasurementUnit.gigaByte.toStringValue(), - 'gigabyte'); - }); - - test('gibibyte', () { - expect(InformationSentryMeasurementUnit.gibiByte.toStringValue(), - 'gibibyte'); - }); - test('terabyte', () { - expect(InformationSentryMeasurementUnit.teraByte.toStringValue(), - 'terabyte'); - }); - test('tebibyte', () { - expect(InformationSentryMeasurementUnit.tebiByte.toStringValue(), - 'tebibyte'); - }); - test('petabyte', () { - expect(InformationSentryMeasurementUnit.petaByte.toStringValue(), - 'petabyte'); - }); - test('pebibyte', () { - expect(InformationSentryMeasurementUnit.pebiByte.toStringValue(), - 'pebibyte'); - }); - test('exabyte', () { - expect(InformationSentryMeasurementUnit.exaByte.toStringValue(), - 'exabyte'); - }); - test('exbibyte', () { - expect(InformationSentryMeasurementUnit.exbiByte.toStringValue(), - 'exbibyte'); - }); - }); - - group('Custom', () { - test('custom', () { - expect(CustomSentryMeasurementUnit('custom').toStringValue(), 'custom'); - }); - }); - }); -} diff --git a/dart/test/sentry_options_test.dart b/dart/test/sentry_options_test.dart deleted file mode 100644 index 25402e2f74..0000000000 --- a/dart/test/sentry_options_test.dart +++ /dev/null @@ -1,186 +0,0 @@ -import 'package:http/http.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/noop_client.dart'; -import 'package:sentry/src/version.dart'; -import 'package:test/test.dart'; - -import 'test_utils.dart'; - -void main() { - test('$Client is NoOp', () { - final options = defaultTestOptions(); - expect(NoOpClient(), options.httpClient); - }); - - test('$Client sets a custom client', () { - final options = defaultTestOptions(); - - final client = Client(); - options.httpClient = client; - expect(client, options.httpClient); - }); - - test('maxBreadcrumbs is 100 by default', () { - final options = defaultTestOptions(); - - expect(100, options.maxBreadcrumbs); - }); - - test('maxBreadcrumbs sets custom maxBreadcrumbs', () { - final options = defaultTestOptions(); - options.maxBreadcrumbs = 200; - - expect(200, options.maxBreadcrumbs); - }); - - test('SdkLogger sets a diagnostic logger', () { - final options = defaultTestOptions(); - expect(options.log, noOpLog); - options.debug = true; - - expect(options.log, isNot(noOpLog)); - }); - - test('setting debug correctly sets logger', () { - final options = defaultTestOptions(); - expect(options.log, noOpLog); - expect(options.diagnosticLog, isNull); - options.debug = true; - expect(options.log, isNot(options.debugLog)); - expect(options.diagnosticLog!.logger, options.debugLog); - expect(options.log, options.diagnosticLog!.log); - - options.debug = false; - expect(options.log, isNot(noOpLog)); - expect(options.diagnosticLog!.logger, noOpLog); - expect(options.log, options.diagnosticLog!.log); - - options.debug = true; - expect(options.log, isNot(options.debugLog)); - expect(options.diagnosticLog!.logger, options.debugLog); - expect(options.log, options.diagnosticLog!.log); - }); - - test('tracesSampler is null by default', () { - final options = defaultTestOptions(); - - expect(options.tracesSampler, isNull); - }); - - test('tracesSampleRate is null by default', () { - final options = defaultTestOptions(); - - expect(options.tracesSampleRate, isNull); - }); - - test('isTracingEnabled is disabled', () { - final options = defaultTestOptions(); - - expect(options.isTracingEnabled(), false); - }); - - test('isTracingEnabled is enabled by theres rate', () { - final options = defaultTestOptions(); - options.tracesSampleRate = 1.0; - - expect(options.isTracingEnabled(), true); - }); - - test('isTracingEnabled is enabled by theres sampler', () { - final options = defaultTestOptions(); - - double? sampler(SentrySamplingContext samplingContext) => 0.0; - - options.tracesSampler = sampler; - - expect(options.isTracingEnabled(), true); - }); - - test('SentryOptions empty inits the late var', () { - final options = SentryOptions.empty(); - options.sdk.addPackage('test', '1.2.3'); - - expect( - options.sdk.packages - .where((element) => - element.name == 'test' && element.version == '1.2.3') - .isNotEmpty, - true); - }); - - test('SentryOptions has all targets by default', () { - final options = SentryOptions.empty(); - - expect(options.tracePropagationTargets, ['.*']); - }); - - test('SentryOptions has sentryClientName set', () { - final options = defaultTestOptions(); - - expect(options.sentryClientName, - '${sdkName(options.platform.isWeb)}/$sdkVersion'); - }); - - test('SentryOptions has default idleTimeout', () { - final options = SentryOptions.empty(); - - expect(options.idleTimeout?.inSeconds, Duration(seconds: 3).inSeconds); - }); - - test('Spotlight is disabled by default', () { - final options = defaultTestOptions(); - - expect(options.spotlight.enabled, false); - }); - - test('enableExceptionTypeIdentification is enabled by default', () { - final options = defaultTestOptions(); - - expect(options.enableExceptionTypeIdentification, true); - }); - - test('enablePureDartSymbolication is enabled by default', () { - final options = defaultTestOptions(); - - expect(options.enableDartSymbolication, true); - }); - - test('diagnosticLevel is warning by default', () { - final options = defaultTestOptions(); - - expect(options.diagnosticLevel, SentryLevel.warning); - }); - - test('parsedDsn is correctly parsed and cached', () { - final options = defaultTestOptions(); - - // Access parsedDsn for the first time - final parsedDsn1 = options.parsedDsn; - - // Access parsedDsn again - final parsedDsn2 = options.parsedDsn; - - // Should return the same instance since it's cached - expect(identical(parsedDsn1, parsedDsn2), isTrue); - - // Verify the parsed DSN fields - final manuallyParsedDsn = Dsn.parse(options.dsn!); - expect(parsedDsn1.publicKey, manuallyParsedDsn.publicKey); - expect(parsedDsn1.postUri, manuallyParsedDsn.postUri); - expect(parsedDsn1.secretKey, manuallyParsedDsn.secretKey); - expect(parsedDsn1.projectId, manuallyParsedDsn.projectId); - expect(parsedDsn1.uri, manuallyParsedDsn.uri); - }); - - test('parsedDsn throws when DSN is null', () { - final options = defaultTestOptions()..dsn = null; - - expect(() => options.parsedDsn, throwsA(isA())); - }); - - test('parsedDsn throws when DSN is empty', () { - final options = defaultTestOptions()..dsn = ''; - - expect(() => options.parsedDsn, throwsA(isA())); - }); -} diff --git a/dart/test/sentry_run_zoned_guarded_test.dart b/dart/test/sentry_run_zoned_guarded_test.dart deleted file mode 100644 index 1fd06310dd..0000000000 --- a/dart/test/sentry_run_zoned_guarded_test.dart +++ /dev/null @@ -1,123 +0,0 @@ -@TestOn('vm') -library; - -import 'dart:async'; - -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/sentry_run_zoned_guarded.dart'; -import 'package:test/test.dart'; - -import 'mocks/mock_hub.dart'; -import 'mocks/mock_sentry_client.dart'; -import 'test_utils.dart'; - -void main() { - group("$SentryRunZonedGuarded", () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('calls onError', () async { - final error = StateError("StateError"); - var onErrorCalled = false; - - final completer = Completer(); - SentryRunZonedGuarded.sentryRunZonedGuarded( - fixture.hub, - () { - throw error; - }, - (error, stackTrace) { - onErrorCalled = true; - completer.complete(); - }, - ); - - await completer.future; - - expect(onErrorCalled, true); - }); - - test('calls zoneSpecification print', () async { - var printCalled = false; - final completer = Completer(); - - final zoneSpecification = ZoneSpecification( - print: (self, parent, zone, line) { - printCalled = true; - completer.complete(); - }, - ); - SentryRunZonedGuarded.sentryRunZonedGuarded( - fixture.hub, - () { - print('foo'); - }, - null, - zoneSpecification: zoneSpecification, - ); - - await completer.future; - - expect(printCalled, true); - }); - - test('marks transaction as internal error if no status', () async { - final exception = StateError('error'); - - final client = MockSentryClient(); - final hub = Hub(fixture.options); - hub.bindClient(client); - hub.startTransaction('name', 'operation', bindToScope: true); - - final completer = Completer(); - SentryRunZonedGuarded.sentryRunZonedGuarded( - hub, - () { - throw exception; - }, - (error, stackTrace) { - completer.complete(); - }, - ); - - await completer.future; - - final span = hub.getSpan(); - expect(span?.status, const SpanStatus.internalError()); - await span?.finish(); - }); - - test('sets level to error instead of fatal', () async { - final client = MockSentryClient(); - final hub = Hub(fixture.options); - hub.bindClient(client); - fixture.options.markAutomaticallyCollectedErrorsAsFatal = false; - - final exception = StateError('error'); - - final completer = Completer(); - SentryRunZonedGuarded.sentryRunZonedGuarded( - hub, - () { - throw exception; - }, - (error, stackTrace) { - completer.complete(); - }, - ); - - await completer.future; - - final capturedEvent = client.captureEventCalls.last.event; - expect(capturedEvent.level, SentryLevel.error); - }); - }); -} - -class Fixture { - final hub = MockHub(); - final options = defaultTestOptions()..tracesSampleRate = 1.0; -} diff --git a/dart/test/sentry_span_context_test.dart b/dart/test/sentry_span_context_test.dart deleted file mode 100644 index 81e651dc6e..0000000000 --- a/dart/test/sentry_span_context_test.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - final fixture = Fixture(); - - test('toJson serializes', () { - final sut = fixture.getSut(); - - final map = sut.toJson(); - - expect(map['span_id'], isNotNull); - expect(map['trace_id'], isNotNull); - expect(map['op'], 'op'); - expect(map['parent_span_id'], isNotNull); - expect(map['description'], 'desc'); - expect(map['origin'], 'manual'); - }); - - test('toTraceContext gets sampled, status, and origin', () { - final sut = fixture.getSut(); - final aborted = SpanStatus.aborted(); - final traceContext = sut.toTraceContext( - sampled: true, - status: aborted, - ); - - expect(traceContext.sampled, true); - expect(traceContext.spanId, isNotNull); - expect(traceContext.traceId, isNotNull); - expect(traceContext.operation, 'op'); - expect(traceContext.parentSpanId, isNotNull); - expect(traceContext.description, 'desc'); - expect(traceContext.status, aborted); - expect(traceContext.origin, 'manual'); - }); -} - -class Fixture { - SentrySpanContext getSut() { - return SentrySpanContext( - operation: 'op', - parentSpanId: SpanId.newId(), - description: 'desc', - origin: 'manual'); - } -} diff --git a/dart/test/sentry_span_test.dart b/dart/test/sentry_span_test.dart deleted file mode 100644 index 7b3486baea..0000000000 --- a/dart/test/sentry_span_test.dart +++ /dev/null @@ -1,316 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:test/test.dart'; - -import 'mocks/mock_hub.dart'; - -void main() { - final fixture = Fixture(); - - test('convert given startTimestamp to utc date time', () async { - final nonUtcStartTimestamp = DateTime.now().toLocal(); - - final sut = fixture.getSut(startTimestamp: nonUtcStartTimestamp); - - expect(nonUtcStartTimestamp.isUtc, false); - expect(sut.startTimestamp.isUtc, true); - }); - - test('convert given endTimestamp to utc date time', () async { - final nonUtcEndTimestamp = DateTime.now().toLocal(); - - final sut = fixture.getSut(startTimestamp: nonUtcEndTimestamp); - - await sut.finish(endTimestamp: nonUtcEndTimestamp); - - expect(nonUtcEndTimestamp.isUtc, false); - expect(sut.endTimestamp?.isUtc, true); - }); - - test('finish sets status', () async { - final sut = fixture.getSut(); - - await sut.finish(status: SpanStatus.aborted()); - - expect(sut.status, SpanStatus.aborted()); - }); - - test('finish sets end timestamp', () async { - final sut = fixture.getSut(); - expect(sut.endTimestamp, isNull); - await sut.finish(); - - expect(sut.endTimestamp, isNotNull); - }); - - test('finish uses given end timestamp', () async { - final sut = fixture.getSut(); - final endTimestamp = getUtcDateTime(); - - expect(sut.endTimestamp, isNull); - await sut.finish(endTimestamp: endTimestamp); - expect(sut.endTimestamp, endTimestamp); - }); - - test('finish sets throwable', () async { - final sut = fixture.getSut(); - sut.throwable = StateError('message'); - - await sut.finish(); - - expect(fixture.hub.spanContextCals, 1); - }); - - test( - 'finish does not set endTimestamp if given end timestamp is before start timestamp', - () async { - final sut = fixture.getSut(); - - final invalidEndTimestamp = sut.startTimestamp.add(-Duration(hours: 1)); - await sut.finish(endTimestamp: invalidEndTimestamp); - - expect(sut.endTimestamp, isNot(equals(invalidEndTimestamp))); - }); - - test('span adds data', () { - final sut = fixture.getSut(); - - sut.setData('test', 'test'); - - expect(sut.data['test'], 'test'); - }); - - test('span removes data', () { - final sut = fixture.getSut(); - - sut.setData('test', 'test'); - sut.removeData('test'); - - expect(sut.data['test'], isNull); - }); - - test('span sets origin', () { - final sut = fixture.getSut(); - - sut.origin = 'manual'; - - expect(sut.origin, 'manual'); - }); - - test('span adds tag', () { - final sut = fixture.getSut(); - - sut.setTag('test', 'test'); - - expect(sut.tags['test'], 'test'); - }); - - test('span removes tags', () { - final sut = fixture.getSut(); - - sut.setTag('test', 'test'); - sut.removeTag('test'); - - expect(sut.tags['test'], isNull); - }); - - test('span starts child', () { - final sut = fixture.getSut(); - - final child = sut.startChild('op', description: 'desc'); - - expect(child.context.parentSpanId, fixture.context.spanId); - expect(child.context.operation, 'op'); - expect(child.context.description, 'desc'); - }); - - test('span serializes', () async { - final sut = fixture.getSut(); - - sut.setTag('test', 'test'); - sut.setData('test', 'test'); - sut.origin = 'manual'; - - await sut.finish(status: SpanStatus.aborted()); - - final map = sut.toJson(); - - expect(map['start_timestamp'], isNotNull); - expect(map['timestamp'], isNotNull); - expect(map['data']['test'], 'test'); - expect(map['tags']['test'], 'test'); - expect(map['status'], 'aborted'); - expect(map['origin'], 'manual'); - }); - - test('finished returns false if not yet', () { - final sut = fixture.getSut(); - - expect(sut.finished, false); - }); - - test('finished returns true if finished', () async { - final sut = fixture.getSut(); - await sut.finish(); - - expect(sut.finished, true); - }); - - test('toSentryTrace returns trace header', () { - final sut = fixture.getSut(); - - expect(sut.toSentryTrace().value, - '${sut.context.traceId}-${sut.context.spanId}-1'); - }); - - test('finish isnt allowed to be called twice', () async { - final sut = fixture.getSut(); - - await sut.finish(status: SpanStatus.ok()); - await sut.finish(status: SpanStatus.cancelled()); - - expect(sut.status, SpanStatus.ok()); - }); - - test('removeData isnt allowed to be called after finishing', () async { - final sut = fixture.getSut(); - - sut.setData('key', 'value'); - await sut.finish(status: SpanStatus.ok()); - sut.removeData('key'); - - expect(sut.data['key'], 'value'); - }); - - test('removeTag isnt allowed to be called after finishing', () async { - final sut = fixture.getSut(); - - sut.setTag('key', 'value'); - await sut.finish(status: SpanStatus.ok()); - sut.removeTag('key'); - - expect(sut.tags['key'], 'value'); - }); - - test('setData isnt allowed to be called after finishing', () async { - final sut = fixture.getSut(); - - sut.setData('key', 'value'); - await sut.finish(status: SpanStatus.ok()); - sut.setData('key', 'value2'); - - expect(sut.data['key'], 'value'); - }); - - test('setTag isnt allowed to be called after finishing', () async { - final sut = fixture.getSut(); - - sut.setTag('key', 'value'); - await sut.finish(status: SpanStatus.ok()); - sut.setTag('key', 'value2'); - - expect(sut.tags['key'], 'value'); - }); - - test('startChild isnt allowed to be called after finishing', () async { - final sut = fixture.getSut(); - - await sut.finish(status: SpanStatus.ok()); - final span = sut.startChild('op'); - - expect(NoOpSentrySpan(), span); - }); - - test( - 'startChild isnt allowed to be called if childs startTimestamp is before parents', - () async { - final parentStartTimestamp = DateTime.now(); - final childStartTimestamp = parentStartTimestamp.add(-Duration(hours: 1)); - final sut = fixture.getSut(startTimestamp: parentStartTimestamp); - - final span = sut.startChild('op', startTimestamp: childStartTimestamp); - - expect(NoOpSentrySpan(), span); - }); - - test('callback called on finish', () async { - var numberOfCallbackCalls = 0; - final passedHint = Hint(); - - final sut = fixture.getSut( - finishedCallback: ({DateTime? endTimestamp, Hint? hint}) async { - expect(passedHint, hint); - numberOfCallbackCalls += 1; - }); - - await sut.finish(hint: passedHint); - - expect(numberOfCallbackCalls, 1); - }); - - test('optional endTimestamp set instead of current time', () async { - final sut = fixture.getSut(); - - final endTimestamp = getUtcDateTime().add(Duration(days: 1)); - - await sut.finish(endTimestamp: endTimestamp); - - expect(sut.endTimestamp, endTimestamp); - }); - - test('child span reschedule finish timer', () async { - final sut = fixture.getSut(autoFinishAfter: Duration(seconds: 5)); - - final currentTimer = fixture.tracer.autoFinishAfterTimer!; - - sut.scheduleFinish(); - - final newTimer = fixture.tracer.autoFinishAfterTimer!; - - expect(currentTimer, isNot(equals(newTimer))); - }); - - test('takes origin from context', () async { - final sut = fixture.getSut(); - expect(sut.origin, 'manual'); - }); - - test('setMeasurement sets a measurement', () async { - final sut = fixture.getSut(); - sut.setMeasurement("test", 1); - expect(sut.tracer.measurements.containsKey("test"), true); - expect(sut.tracer.measurements["test"]!.value, 1); - }); - - test('setMeasurement does not set a measurement if a span is finished', - () async { - final sut = fixture.getSut(); - await sut.finish(); - sut.setMeasurement("test", 1); - expect(sut.tracer.measurements.isEmpty, true); - }); -} - -class Fixture { - final context = SentryTransactionContext('name', 'op', origin: 'manual'); - late SentryTracer tracer; - final hub = MockHub(); - - SentrySpan getSut({ - DateTime? startTimestamp, - bool? sampled = true, - OnFinishedCallback? finishedCallback, - Duration? autoFinishAfter, - }) { - tracer = SentryTracer(context, hub, autoFinishAfter: autoFinishAfter); - - return SentrySpan( - tracer, - context, - hub, - startTimestamp: startTimestamp, - samplingDecision: SentryTracesSamplingDecision(sampled!), - finishedCallback: finishedCallback, - ); - } -} diff --git a/dart/test/sentry_template_string_test.dart b/dart/test/sentry_template_string_test.dart deleted file mode 100644 index 0897c25ea5..0000000000 --- a/dart/test/sentry_template_string_test.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'package:sentry/src/sentry_template_string.dart'; -import 'package:test/test.dart'; - -void main() { - final fixture = Fixture(); - - group('SentryTemplateString', () { - test('basic string replacement', () { - final sut = fixture.getSut("Hello, %s!", ['John']); - final result = sut.format(); - - expect(result, 'Hello, John!'); - }); - - test('multiple string replacements', () { - final sut = - fixture.getSut("Hello, %s! You are %s years old.", ['John', '25']); - final result = sut.format(); - - expect(result, 'Hello, John! You are 25 years old.'); - }); - - test('bool argument', () { - final sut = fixture.getSut("The value is %s", [true]); - final result = sut.format(); - - expect(result, 'The value is true'); - }); - - test('int argument', () { - final sut = fixture.getSut("The number is %s", [42]); - final result = sut.format(); - - expect(result, 'The number is 42'); - }); - - test('double argument', () { - final sut = fixture.getSut("The decimal is %s", [3.14]); - final result = sut.format(); - - expect(result, 'The decimal is 3.14'); - }); - - test('mixed argument types', () { - final sut = fixture.getSut("Name: %s, Age: %s, Active: %s, Score: %s", - ['Alice', 30, true, 95.5]); - final result = sut.format(); - - expect(result, 'Name: Alice, Age: 30, Active: true, Score: 95.5'); - }); - - test('not enough arguments - replace with empty string', () { - final sut = fixture.getSut("Hello, %s! You are %s years old.", ['John']); - final result = sut.format(); - - expect(result, 'Hello, John! You are years old.'); - }); - - test('empty arguments trigger assertion error', () { - final sut = fixture.getSut("Hello, %s! You are %s years old.", []); - - expect(() => sut.format(), throwsA(isA())); - }); - - test('no placeholder strings trigger assertion error', () { - final sut = fixture.getSut("Hello, World!", ['ignored']); - - expect(() => sut.format(), throwsA(isA())); - }); - - test('too many arguments - ignore extras', () { - final sut = fixture.getSut("Hello, %s!", ['John', 'extra', 'arguments']); - final result = sut.format(); - - expect(result, 'Hello, John!'); - }); - - test('unsupported type with toString()', () { - final sut = - fixture.getSut("The object is %s", [CustomObject('test value')]); - final result = sut.format(); - - expect(result, 'The object is CustomObject: test value'); - }); - - test('unsupported type with throwing toString() falls back to empty string', - () { - final sut = - fixture.getSut("The object is %s", [ThrowingToStringObject()]); - final result = sut.format(); - - expect(result, 'The object is '); - }); - - test('unsupported type with default toString()', () { - final sut = - fixture.getSut("The object is %s", [NoToStringMethodObject()]); - final result = sut.format(); - - expect(result, 'The object is Instance of \'NoToStringMethodObject\''); - }); - - test('template with escaped %%s', () { - final sut = fixture.getSut("The percentage is %s%%", [50]); - final result = sut.format(); - - expect(result, 'The percentage is 50%'); - }); - - test('template with literal % character', () { - final sut = fixture - .getSut("The percentage is 50%%, with no extra %s", ['values']); - final result = sut.format(); - - expect(result, 'The percentage is 50%, with no extra values'); - }); - - test('toString() prints formatted string', () { - final sut = fixture.getSut("Hello, %s!", ['John']); - final result = sut.toString(); - - expect(result, 'Hello, John!'); - }); - }); -} - -class Fixture { - SentryTemplateString getSut(String template, List arguments) { - return SentryTemplateString(template, arguments); - } -} - -class CustomObject { - final String value; - - CustomObject(this.value); - - @override - String toString() { - return 'CustomObject: $value'; - } -} - -class ThrowingToStringObject { - @override - String toString() { - throw Exception('toString() is broken'); - } -} - -class NoToStringMethodObject { - var foo = "bar"; -} diff --git a/dart/test/sentry_test.dart b/dart/test/sentry_test.dart deleted file mode 100644 index 9230d71396..0000000000 --- a/dart/test/sentry_test.dart +++ /dev/null @@ -1,683 +0,0 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'dart:async'; -import 'dart:isolate'; - -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/dart_exception_type_identifier.dart'; -import 'package:sentry/src/event_processor/deduplication_event_processor.dart'; -import 'package:sentry/src/logs_enricher_integration.dart'; -import 'package:sentry/src/feature_flags_integration.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'mocks/mock_integration.dart'; -import 'mocks/mock_runtime_checker.dart'; -import 'mocks/mock_sentry_client.dart'; -import 'test_utils.dart'; - -AppRunner appRunner = () {}; - -void main() { - group('Sentry capture methods', () { - var client = MockSentryClient(); - - var anException = Exception(); - late SentryEvent fakeEvent; - late SentryMessage fakeMessage; - - setUp(() async { - final options = defaultTestOptions(); - await Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - options.tracesSampleRate = 1.0; - }, - ); - anException = Exception('anException'); - fakeEvent = getFakeEvent(); - fakeMessage = getFakeMessage(); - client = MockSentryClient(); - Sentry.bindClient(client); - }); - - tearDown(() async { - await Sentry.close(); - }); - - test('should capture the event', () async { - await Sentry.captureEvent(fakeEvent); - - expect(client.captureEventCalls.length, 1); - expect(client.captureEventCalls.first.event, fakeEvent); - expect(client.captureEventCalls.first.scope, isNotNull); - }); - - test('should capture the feedback event', () async { - final fakeFeedback = SentryFeedback(message: 'message'); - await Sentry.captureFeedback(fakeFeedback); - - expect(client.captureFeedbackCalls.length, 1); - expect(client.captureFeedbackCalls.first.feedback, fakeFeedback); - expect(client.captureFeedbackCalls.first.scope, isNotNull); - }); - - test('should capture the event withScope', () async { - await Sentry.captureEvent( - fakeEvent, - withScope: (scope) { - scope.setUser(SentryUser(id: 'foo bar')); - }, - ); - - expect(client.captureEventCalls.length, 1); - expect(client.captureEventCalls.first.event, fakeEvent); - expect(client.captureEventCalls.first.scope?.user?.id, 'foo bar'); - }); - - test('should capture the feedback event withScope', () async { - final fakeFeedback = SentryFeedback(message: 'message'); - await Sentry.captureFeedback( - fakeFeedback, - withScope: (scope) { - scope.setUser(SentryUser(id: 'foo bar')); - }, - ); - - expect(client.captureFeedbackCalls.length, 1); - expect(client.captureFeedbackCalls.first.scope?.user?.id, 'foo bar'); - }); - - test('should not capture a null exception', () async { - await Sentry.captureException(null); - expect(client.captureEventCalls.length, 0); - }); - - test('should capture the exception', () async { - await Sentry.captureException(anException); - expect(client.captureEventCalls.length, 1); - expect(client.captureEventCalls.first.event.throwable, anException); - expect(client.captureEventCalls.first.stackTrace, isNull); - expect(client.captureEventCalls.first.scope, isNotNull); - }); - - test('should capture exception with message', () async { - await Sentry.captureException(anException, - message: SentryMessage('Sentry rocks')); - - expect(client.captureEventCalls.first.event.message?.formatted, - 'Sentry rocks'); - }); - - test('should capture exception withScope', () async { - await Sentry.captureException(anException, withScope: (scope) { - scope.setUser(SentryUser(id: 'foo bar')); - }); - expect(client.captureEventCalls.length, 1); - expect(client.captureEventCalls.first.event.throwable, anException); - expect(client.captureEventCalls.first.scope?.user?.id, 'foo bar'); - }); - - test('should capture message', () async { - await Sentry.captureMessage( - fakeMessage.formatted, - level: SentryLevel.warning, - ); - - expect(client.captureMessageCalls.length, 1); - expect(client.captureMessageCalls.first.formatted, fakeMessage.formatted); - expect(client.captureMessageCalls.first.level, SentryLevel.warning); - }); - - test('should capture message withScope', () async { - await Sentry.captureMessage( - fakeMessage.formatted, - withScope: (scope) { - scope.setUser(SentryUser(id: 'foo bar')); - }, - ); - - expect(client.captureMessageCalls.length, 1); - expect(client.captureMessageCalls.first.formatted, fakeMessage.formatted); - expect(client.captureMessageCalls.first.scope?.user?.id, 'foo bar'); - }); - - test('should start transaction with given values', () async { - final tr = Sentry.startTransaction('name', 'op'); - await tr.finish(); - - expect(client.captureTransactionCalls.length, 1); - }); - - test('should start transaction with context', () async { - final tr = Sentry.startTransactionWithContext( - SentryTransactionContext('name', 'operation')); - await tr.finish(); - - expect(client.captureTransactionCalls.length, 1); - }); - - test('should start transaction with hint', () async { - final tr = Sentry.startTransactionWithContext( - SentryTransactionContext('name', 'operation')); - await tr.finish(); - - expect(client.captureTransactionCalls.length, 1); - }); - - test('should return span if bound to the scope', () async { - final tr = Sentry.startTransaction('name', 'op', bindToScope: true); - - expect(Sentry.getSpan(), tr); - }); - - test('should not return span if not bound to the scope', () async { - Sentry.startTransaction('name', 'op'); - - expect(Sentry.getSpan(), isNull); - }); - }); - - group('Sentry is enabled or disabled', () { - setUp(() async { - await Sentry.close(); - }); - - test('null DSN', () async { - final options = defaultTestOptions(); - expect( - () async => await Sentry.init( - options: options, - (options) => options.dsn = null, - ), - throwsArgumentError, - ); - expect(Sentry.isEnabled, false); - }); - - test('appRunner should be optional', () async { - expect(Sentry.isEnabled, false); - final options = defaultTestOptions(); - await Sentry.init( - options: options, - (options) => options.dsn = fakeDsn, - ); - expect(Sentry.isEnabled, true); - }); - - test('empty DSN', () async { - final options = defaultTestOptions(); - await Sentry.init( - options: options, - (options) => options.dsn = '', - ); - expect(Sentry.isEnabled, false); - }); - - test('empty DSN disables the SDK but runs the integrations', () async { - final integration = MockIntegration(); - - final options = defaultTestOptions(); - await Sentry.init( - options: options, - (options) { - options.dsn = ''; - options.addIntegration(integration); - }, - ); - - expect(integration.callCalls, 1); - }); - - test('close disables the SDK', () async { - final options = defaultTestOptions(); - await Sentry.init( - options: options, - (options) => options.dsn = fakeDsn, - ); - - Sentry.bindClient(MockSentryClient()); - - expect(Sentry.isEnabled, true); - - await Sentry.close(); - - expect(Sentry.isEnabled, false); - }); - }); - - group('Sentry init', () { - tearDown(() async { - await Sentry.close(); - }); - - test('should install integrations', () async { - final integration = MockIntegration(); - - final options = defaultTestOptions(); - await Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - options.addIntegration(integration); - }, - ); - - expect(integration.callCalls, 1); - }); - - test('should add default integrations', () async { - late SentryOptions optionsReference; - final options = defaultTestOptions(); - await Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - optionsReference = options; - }, - appRunner: appRunner, - ); - expect( - optionsReference.integrations - .whereType() - .length, - 1, - ); - expect( - optionsReference.integrations - .whereType() - .length, - 1, - ); - expect( - optionsReference.integrations - .whereType() - .length, - 1, - ); - }, onPlatform: {'browser': Skip()}); - - test('should add logsEnricherIntegration', () async { - late SentryOptions optionsReference; - final options = defaultTestOptions(); - - await Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - optionsReference = options; - }, - appRunner: appRunner, - ); - - expect( - optionsReference.integrations - .whereType() - .length, - 1, - ); - }); - - test('should add only web compatible default integrations', () async { - final options = defaultTestOptions(); - await Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - expect( - options.integrations.whereType().length, - 0, - ); - }, - ); - }, onPlatform: {'vm': Skip()}); - - test('should add feature flag FeatureFlagsIntegration', () async { - await Sentry.init( - options: defaultTestOptions(), - (options) => options.dsn = fakeDsn, - ); - - await Sentry.addFeatureFlag('foo', true); - - expect( - Sentry.currentHub.scope.contexts[SentryFeatureFlags.type]?.values.first - .flag, - equals('foo'), - ); - expect( - Sentry.currentHub.scope.contexts[SentryFeatureFlags.type]?.values.first - .result, - equals(true), - ); - }); - - test('addFeatureFlag should ignore non-boolean values', () async { - await Sentry.init( - options: defaultTestOptions(), - (options) => options.dsn = fakeDsn, - ); - - await Sentry.addFeatureFlag('foo1', 'some string'); - await Sentry.addFeatureFlag('foo2', 123); - await Sentry.addFeatureFlag('foo3', 1.23); - - final featureFlagsContext = - Sentry.currentHub.scope.contexts[SentryFeatureFlags.type]; - expect(featureFlagsContext, isNull); - }); - - test('should close integrations', () async { - final integration = MockIntegration(); - - final options = defaultTestOptions(); - await Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - options.addIntegration(integration); - }, - ); - - await Sentry.close(); - - expect(integration.callCalls, 1); - expect(integration.closeCalls, 1); - }); - - test('$DeduplicationEventProcessor is added on init', () async { - final options = defaultTestOptions(); - await Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - final count = options.eventProcessors - .whereType() - .length; - expect(count, 1); - }, - ); - }); - - test('should complete when appRunner completes', () async { - final completer = Completer(); - var completed = false; - - final options = defaultTestOptions(); - final init = Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - }, - appRunner: () => completer.future, - ).whenComplete(() => completed = true); - - await Future(() { - // We make the expectation only after all microtasks have completed, - // that Sentry.init might have scheduled. - expect(completed, false); - }); - - completer.complete(); - await init; - - expect(completed, true); - }); - - test('should add DartExceptionTypeIdentifier by default', () async { - final options = defaultTestOptions(); - await Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - }, - ); - - expect(options.exceptionTypeIdentifiers.length, 1); - final cachingIdentifier = options.exceptionTypeIdentifiers.first - as CachingExceptionTypeIdentifier; - expect( - cachingIdentifier, - isA().having( - (c) => c.identifier, - 'wrapped identifier', - isA(), - ), - ); - }); - - test('should set options.debug to true when in debug mode', () async { - final options = defaultTestOptions(); - options.runtimeChecker = MockRuntimeChecker(isDebug: true); - - expect(options.debug, isFalse); - await Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - }, - ); - expect(options.debug, isTrue); - }); - - test('should respect user options.debug when in debug mode', () async { - final options = defaultTestOptions(); - options.runtimeChecker = MockRuntimeChecker(isDebug: true); - - expect(options.debug, isFalse); - await Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - options.debug = false; - }, - ); - expect(options.debug, isFalse); - }); - - test('should leave options.debug unchanged when not in debug mode', - () async { - final options = defaultTestOptions(); - options.runtimeChecker = MockRuntimeChecker(isDebug: false); - - expect(options.debug, isFalse); - await Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - }, - ); - expect(options.debug, isFalse); - }); - - test('isolate completes when closing sentry', () async { - final onExit = ReceivePort(); - - Future _runSentry(String message) async { - await Sentry.init((options) { - options - ..dsn = fakeDsn - ..tracesSampleRate = 1.0; - }); - await Sentry.close(); - } - - final completer = Completer(); - - await Isolate.spawn( - _runSentry, - 'test', - onExit: onExit.sendPort, - ); - - var completed = false; - onExit.listen((message) { - completed = true; - completer.complete(); - }); - - await completer.future; - expect(completed, true); - }); - }, testOn: 'vm'); - - test('should complete when appRunner is not called in runZonedGuarded', - () async { - final completer = Completer(); - var completed = false; - - final options = defaultTestOptions(); - final init = Sentry.init( - options: options, - (options) { - options.dsn = fakeDsn; - }, - appRunner: () => completer.future, - callAppRunnerInRunZonedGuarded: false, - ).whenComplete(() => completed = true); - - await Future(() { - // We make the expectation only after all microtasks have completed, - // that Sentry.init might have scheduled. - expect(completed, false); - }); - - completer.complete(); - await init; - - expect(completed, true); - }); - - test('options.environment debug', () async { - final sentryOptions = - defaultTestOptions(checker: MockRuntimeChecker(isDebug: true)); - await Sentry.init( - (options) { - options.dsn = fakeDsn; - expect(options.environment, 'debug'); - expect(options.debug, true); - }, - options: sentryOptions, - ); - }); - - test('options.environment profile', () async { - final sentryOptions = - defaultTestOptions(checker: MockRuntimeChecker(isProfile: true)); - - await Sentry.init( - (options) { - options.dsn = fakeDsn; - expect(options.environment, 'profile'); - expect(options.debug, false); - }, - options: sentryOptions, - ); - }); - - test('options.environment production (defaultEnvironment)', () async { - final sentryOptions = - defaultTestOptions(checker: MockRuntimeChecker(isRelease: true)); - await Sentry.init( - (options) { - options.dsn = fakeDsn; - expect(options.environment, 'production'); - expect(options.debug, false); - }, - options: sentryOptions, - ); - }); - - test('options.logger is set by setting the debug flag', () async { - final sentryOptions = - defaultTestOptions(checker: MockRuntimeChecker(isDebug: true)); - - await Sentry.init( - (options) { - options.dsn = fakeDsn; - options.debug = true; - expect(options.diagnosticLog?.logger, isNot(noOpLog)); - - options.debug = false; - expect(options.diagnosticLog?.logger, noOpLog); - - options.debug = true; - expect(options.diagnosticLog?.logger, isNot(noOpLog)); - }, - options: sentryOptions, - ); - - expect(sentryOptions.diagnosticLog?.logger, isNot(noOpLog)); - }); - - group('Sentry init optionsConfiguration', () { - final fixture = Fixture(); - - tearDown(() async { - await Sentry.close(); - }); - - test('throw is handled and logged', () async { - // Use release mode in runtime checker to avoid additional log - final sentryOptions = - defaultTestOptions(checker: MockRuntimeChecker(isRelease: true)) - ..automatedTestMode = false - ..debug = true - ..log = fixture.mockLogger; - - final exception = Exception("Exception in options callback"); - await Sentry.init( - (options) async { - throw exception; - }, - options: sentryOptions, - ); - - expect(fixture.loggedException, exception); - expect(fixture.loggedLevel, SentryLevel.error); - }); - }); - - group('Sentry runZonedGuarded', () { - test('calling runZonedGuarded before init does not throw', () async { - await Sentry.close(); - - var expected = Exception("run zoned guarded exception"); - Object? actual; - - final completer = Completer(); - Sentry.runZonedGuarded(() { - throw expected; - }, (error, stackTrace) { - actual = error; - completer.complete(); - }); - - await completer.future; - - expect(actual, isNotNull); - expect(actual, expected); - }); - }); -} - -class Fixture { - bool logged = false; - SentryLevel? loggedLevel; - Object? loggedException; - - void mockLogger( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - if (!logged) { - logged = true; // Block multiple calls which override expected values. - loggedLevel = level; - loggedException = exception; - } - } -} diff --git a/dart/test/sentry_trace_context_header_test.dart b/dart/test/sentry_trace_context_header_test.dart deleted file mode 100644 index eb97f87913..0000000000 --- a/dart/test/sentry_trace_context_header_test.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; - -void main() { - group('$SentryTraceContextHeader', () { - final traceId = SentryId.newId(); - - final context = SentryTraceContextHeader( - traceId, - '123', - release: 'release', - environment: 'environment', - userId: 'user_id', - transaction: 'transaction', - sampleRate: '1.0', - sampled: 'false', - replayId: SentryId.fromId('456'), - unknown: testUnknown, - ); - - final mapJson = { - 'trace_id': '$traceId', - 'public_key': '123', - 'release': 'release', - 'environment': 'environment', - 'user_id': 'user_id', - 'transaction': 'transaction', - 'sample_rate': '1.0', - 'sampled': 'false', - 'replay_id': '456', - }; - mapJson.addAll(testUnknown); - - test('fromJson', () { - expect(context.traceId.toString(), traceId.toString()); - expect(context.publicKey, '123'); - expect(context.release, 'release'); - expect(context.environment, 'environment'); - expect(context.userId, 'user_id'); - expect(context.transaction, 'transaction'); - expect(context.sampleRate, '1.0'); - expect(context.sampled, 'false'); - expect(context.replayId, SentryId.fromId('456')); - }); - - test('toJson', () { - final json = context.toJson(); - - expect(MapEquality().equals(json, mapJson), isTrue); - }); - - test('to baggage', () { - final baggage = context.toBaggage(); - - expect( - baggage.toHeaderString(), - 'sentry-trace_id=${traceId.toString()},' - 'sentry-public_key=123,' - 'sentry-release=release,' - 'sentry-environment=environment,' - 'sentry-user_id=user_id,' - 'sentry-transaction=transaction,' - 'sentry-sample_rate=1.0,' - 'sentry-sampled=false,' - 'sentry-replay_id=456', - ); - }); - }); -} diff --git a/dart/test/sentry_trace_context_test.dart b/dart/test/sentry_trace_context_test.dart deleted file mode 100644 index 6f980b00d8..0000000000 --- a/dart/test/sentry_trace_context_test.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/propagation_context.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; - -void main() { - final fixture = Fixture(); - - test('toJson serializes', () { - final sut = fixture.getSut(); - - final map = sut.toJson(); - - expect(map['span_id'], isNotNull); - expect(map['trace_id'], isNotNull); - expect(map['op'], 'op'); - expect(map['parent_span_id'], isNotNull); - expect(map['description'], 'desc'); - expect(map['status'], 'aborted'); - expect(map['origin'], 'auto.ui'); - expect(map['replay_id'], isNotNull); - expect(map['data'], {'key': 'value'}); - }); - - test('fromJson deserializes', () { - final map = { - 'op': 'op', - 'span_id': '0000000000000001', - 'trace_id': '00000000000000000000000000000002', - 'parent_span_id': '0000000000000003', - 'description': 'desc', - 'status': 'aborted', - 'origin': 'auto.ui', - 'replay_id': '00000000000000000000000000000004', - 'data': {'key': 'value'}, - }; - map.addAll(testUnknown); - final traceContext = SentryTraceContext.fromJson(map); - - expect(traceContext.description, 'desc'); - expect(traceContext.operation, 'op'); - expect(traceContext.spanId.toString(), '0000000000000001'); - expect(traceContext.traceId.toString(), '00000000000000000000000000000002'); - expect(traceContext.parentSpanId.toString(), '0000000000000003'); - expect(traceContext.status.toString(), 'aborted'); - expect(traceContext.sampled, true); - expect( - traceContext.replayId.toString(), '00000000000000000000000000000004'); - expect(traceContext.data, {'key': 'value'}); - }); - - test('fromPropagationContext creates valid SentryTraceContext', () { - final propagationContext = PropagationContext(); - - final traceContext1 = - SentryTraceContext.fromPropagationContext(propagationContext); - final traceContext2 = - SentryTraceContext.fromPropagationContext(propagationContext); - - expect(traceContext1.traceId, propagationContext.traceId); - expect(traceContext1.traceId, traceContext2.traceId); - // the span id is always generated new when creating a trace context from scope - expect(traceContext1.spanId, isNot(traceContext2.spanId)); - }); -} - -class Fixture { - SentryTraceContext getSut() { - return SentryTraceContext( - operation: 'op', - parentSpanId: SpanId.newId(), - description: 'desc', - sampled: true, - status: SpanStatus.aborted(), - origin: 'auto.ui', - replayId: SentryId.newId(), - data: {'key': 'value'}, - unknown: testUnknown, - ); - } -} diff --git a/dart/test/sentry_tracer_test.dart b/dart/test/sentry_tracer_test.dart deleted file mode 100644 index 86f6f02ccb..0000000000 --- a/dart/test/sentry_tracer_test.dart +++ /dev/null @@ -1,637 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:test/test.dart'; - -import 'mocks/mock_hub.dart'; -import 'mocks/mock_sentry_client.dart'; -import 'test_utils.dart'; - -void main() { - group('$SentryTracer', () { - late Fixture fixture; - - setUp(() async { - fixture = Fixture(); - }); - - test('tracer sets name', () { - final sut = fixture.getSut(); - - expect(sut.name, 'name'); - }); - - test('tracer sets unsampled', () { - final sut = fixture.getSut(sampled: false); - - expect(sut.samplingDecision?.sampled, isFalse); - }); - - test('tracer sets sampled', () { - final sut = fixture.getSut(sampled: true); - - expect(sut.samplingDecision?.sampled, isTrue); - }); - - test('tracer origin from root span', () { - final sut = fixture.getSut(); - - expect(sut.origin, 'manual'); - }); - - test('tracer finishes with status', () async { - final sut = fixture.getSut(); - - await sut.finish(status: SpanStatus.aborted()); - - final tr = fixture.hub.captureTransactionCalls.first; - final trace = tr.transaction.contexts.trace; - - expect(trace?.status.toString(), 'aborted'); - }); - - test('tracer passes the trace context on finish', () async { - final sut = fixture.getSut(); - - await sut.finish(status: SpanStatus.aborted()); - - final tr = fixture.hub.captureTransactionCalls.first; - - expect(tr.traceContext, isNotNull); - }); - - test('tracer finishes with end timestamp', () async { - final sut = fixture.getSut(); - final endTimestamp = getUtcDateTime(); - - await sut.finish(endTimestamp: endTimestamp); - - expect(sut.endTimestamp, endTimestamp); - }); - - test('tracer does not finish unfinished spans', () async { - final sut = fixture.getSut(); - sut.startChild('child'); - - await sut.finish(status: SpanStatus.aborted()); - - final tr = fixture.hub.captureTransactionCalls.first; - final child = tr.transaction.spans.first; - - expect(child.status, isNull); - expect(child.endTimestamp, isNull); - }); - - test('tracer sets data to extra', () async { - final sut = fixture.getSut(); - - sut.setData('test', 'test'); - - await sut.finish(status: SpanStatus.aborted()); - - final tr = fixture.hub.captureTransactionCalls.first; - - // ignore: deprecated_member_use_from_same_package - expect(tr.transaction.extra?['test'], 'test'); - }); - - test('tracer removes data to extra', () async { - final sut = fixture.getSut(); - - sut.setData('test', 'test'); - sut.removeData('test'); - - await sut.finish(status: SpanStatus.aborted()); - - final tr = fixture.hub.captureTransactionCalls.first; - - // ignore: deprecated_member_use_from_same_package - expect(tr.transaction.extra?['test'], isNull); - }); - - test('tracer sets non-string data to extra', () async { - final sut = fixture.getSut(); - - sut.setData('test', {'key': 'value'}); - - await sut.finish(status: SpanStatus.aborted()); - - final tr = fixture.hub.captureTransactionCalls.first; - - // ignore: deprecated_member_use_from_same_package - expect(tr.transaction.extra?['test'], {'key': 'value'}); - }); - - test('tracer starts child', () async { - final sut = fixture.getSut(); - - final child = sut.startChild('operation', description: 'desc'); - await child.finish(); - - await sut.finish(status: SpanStatus.aborted()); - - final tr = fixture.hub.captureTransactionCalls.first; - final childSpan = tr.transaction.spans.first; - - expect(childSpan.context.description, 'desc'); - expect(childSpan.context.operation, 'operation'); - }); - - test('tracer starts child with parentSpanId', () async { - final sut = fixture.getSut(); - final parentId = SpanId.newId(); - final child = sut.startChildWithParentSpanId( - parentId, - 'op', - description: 'desc', - ); - await child.finish(); - - await sut.finish(status: SpanStatus.aborted()); - - final tr = fixture.hub.captureTransactionCalls.first; - final childSpan = tr.transaction.spans.first; - - expect(childSpan.context.description, 'desc'); - expect(childSpan.context.operation, 'op'); - expect(childSpan.context.parentSpanId.toString(), parentId.toString()); - }); - - test('tracer passes sampled decision to child', () async { - final sut = fixture.getSut(); - final parentId = SpanId.newId(); - final child = sut.startChildWithParentSpanId( - parentId, - 'op', - description: 'desc', - ); - await child.finish(); - - await sut.finish(status: SpanStatus.aborted()); - - expect(child.samplingDecision?.sampled, isTrue); - }); - - test('tracer passes unsampled decision to child', () async { - final sut = fixture.getSut(sampled: false); - final parentId = SpanId.newId(); - final child = sut.startChildWithParentSpanId( - parentId, - 'op', - description: 'desc', - ); - await child.finish(); - - await sut.finish(status: SpanStatus.aborted()); - - expect(child.samplingDecision?.sampled, isFalse); - }); - - test('toSentryTrace returns trace header', () { - final sut = fixture.getSut(); - - expect(sut.toSentryTrace().value, - '${sut.context.traceId}-${sut.context.spanId}-1'); - }); - - test('finish isnt allowed to be called twice', () async { - final sut = fixture.getSut(); - - await sut.finish(status: SpanStatus.ok()); - await sut.finish(status: SpanStatus.cancelled()); - - expect(sut.status, SpanStatus.ok()); - }); - - test('removeData isnt allowed to be called after finishing', () async { - final sut = fixture.getSut(); - - sut.setData('key', 'value'); - await sut.finish(status: SpanStatus.ok()); - sut.removeData('key'); - - expect(sut.data['key'], 'value'); - }); - - test('removeTag isnt allowed to be called after finishing', () async { - final sut = fixture.getSut(); - - sut.setTag('key', 'value'); - await sut.finish(status: SpanStatus.ok()); - sut.removeTag('key'); - - expect(sut.tags['key'], 'value'); - }); - - test('setData isnt allowed to be called after finishing', () async { - final sut = fixture.getSut(); - - sut.setData('key', 'value'); - await sut.finish(status: SpanStatus.ok()); - sut.setData('key', 'value2'); - - expect(sut.data['key'], 'value'); - }); - - test('setTag isnt allowed to be called after finishing', () async { - final sut = fixture.getSut(); - - sut.setTag('key', 'value'); - await sut.finish(status: SpanStatus.ok()); - sut.setTag('key', 'value2'); - - expect(sut.tags['key'], 'value'); - }); - - test('startChild isnt allowed to be called after finishing', () async { - final sut = fixture.getSut(); - - await sut.finish(status: SpanStatus.ok()); - final span = sut.startChild('op'); - - expect(NoOpSentrySpan(), span); - }); - - test('tracer finishes after auto finish duration', () async { - final sut = fixture.getSut(autoFinishAfter: Duration(milliseconds: 200)); - - expect(sut.finished, false); - await Future.delayed(Duration(milliseconds: 210)); - expect(sut.status, SpanStatus.ok()); - expect(sut.finished, true); - }); - - test('tracer reschedule finish timer', () async { - final sut = fixture.getSut(autoFinishAfter: Duration(milliseconds: 200)); - - final currentTimer = sut.autoFinishAfterTimer!; - - sut.scheduleFinish(); - - final newTimer = sut.autoFinishAfterTimer!; - - expect(currentTimer, isNot(equals(newTimer))); - }); - - test('tracer do not reschedule if finished', () async { - final sut = fixture.getSut(autoFinishAfter: Duration(milliseconds: 200)); - - final currentTimer = sut.autoFinishAfterTimer!; - - await sut.finish(); - - sut.scheduleFinish(); - - final newTimer = sut.autoFinishAfterTimer!; - - expect(currentTimer, newTimer); - }); - - test('tracer finish needs child to finish', () async { - final sut = fixture.getSut(waitForChildren: true); - - final child = sut.startChild('operation', description: 'description'); - - await sut.finish(); - expect(sut.finished, false); - - await child.finish(); - expect(sut.finished, true); - }); - - test('tracer finish needs all children to finish', () async { - final sut = fixture.getSut(waitForChildren: true); - - final childA = sut.startChild('operation-a', description: 'description'); - final childB = sut.startChild('operation-b', description: 'description'); - - await sut.finish(); - expect(sut.finished, false); - - await childA.finish(); - expect(sut.finished, false); - - await childB.finish(); - expect(sut.finished, true); - }); - - test( - 'tracer without finish will not be finished when children are finished', - () async { - final sut = fixture.getSut(waitForChildren: true); - - final childA = sut.startChild('operation-a', description: 'description'); - final childB = sut.startChild('operation-b', description: 'description'); - - await childA.finish(); - expect(sut.finished, false); - - await childB.finish(); - expect(sut.finished, false); - - await sut.finish(); - expect(sut.finished, true); - }); - - test('end trimmed to last child', () async { - final sut = fixture.getSut(trimEnd: true); - final endTimestamp = getUtcDateTime().add(Duration(minutes: 1)); - final olderEndTimeStamp = endTimestamp.add(Duration(seconds: 1)); - final oldestEndTimeStamp = olderEndTimeStamp.add(Duration(seconds: 1)); - - final childA = sut.startChild('operation-a', description: 'description'); - final childB = sut.startChild('operation-b', description: 'description'); - - await childA.finish(endTimestamp: endTimestamp); - await childB.finish(endTimestamp: olderEndTimeStamp); - await sut.finish(endTimestamp: oldestEndTimeStamp); - - expect(sut.endTimestamp, childB.endTimestamp); - }); - - test('end trimmed to child', () async { - final sut = fixture.getSut(trimEnd: true); - final endTimestamp = getUtcDateTime().add(Duration(minutes: 1)); - final olderEndTimeStamp = endTimestamp.add(Duration(seconds: 1)); - - final childA = sut.startChild('operation-a', description: 'description'); - - await childA.finish(endTimestamp: endTimestamp); - await sut.finish(endTimestamp: olderEndTimeStamp); - - expect(sut.endTimestamp, childA.endTimestamp); - }); - - test('end not trimmed when no child', () async { - final sut = fixture.getSut(trimEnd: true); - final endTimestamp = getUtcDateTime(); - - await sut.finish(endTimestamp: endTimestamp); - - expect(sut.endTimestamp, endTimestamp); - }); - - test('end trimmed to latest child end timestamp', () async { - final sut = fixture.getSut(trimEnd: true); - final rootEndInitial = getUtcDateTime(); - - final childAEnd = rootEndInitial; - final childBEnd = rootEndInitial.add(Duration(seconds: 1)); - final childCEnd = rootEndInitial; - - final childA = sut.startChild('operation-a', description: 'description'); - final childB = sut.startChild('operation-b', description: 'description'); - final childC = sut.startChild('operation-c', description: 'description'); - - await childA.finish(endTimestamp: childAEnd); - await childB.finish(endTimestamp: childBEnd); - await childC.finish(endTimestamp: childCEnd); - - await sut.finish(endTimestamp: rootEndInitial); - - expect(sut.endTimestamp, equals(childB.endTimestamp), - reason: - 'The root end timestamp should be updated to match the latest child end timestamp.'); - }); - - test('does not add more spans than configured in options', () async { - fixture.hub.options.maxSpans = 2; - final sut = fixture.getSut(); - - sut.startChild('child1'); - sut.startChild('child2'); - sut.startChild('child3'); - - expect(sut.children.length, 2); - }); - - test('when span limit is reached, startChild returns NoOpSpan', () async { - fixture.hub.options.maxSpans = 2; - final sut = fixture.getSut(); - - sut.startChild('child1'); - sut.startChild('child2'); - - expect(sut.startChild('child3'), isA()); - }); - - test('do not capture idle transaction without children', () async { - final sut = fixture.getSut(autoFinishAfter: Duration(milliseconds: 200)); - - await sut.finish(); - - expect(fixture.hub.captureTransactionCalls.isEmpty, true); - }); - - test('tracer sets measurement', () async { - final sut = fixture.getSut(); - - sut.setMeasurement('key', 1.0); - - expect(sut.measurements['key']!.value, 1.0); - - await sut.finish(); - }); - - test('tracer sets custom measurement unit', () async { - final sut = fixture.getSut(); - - sut.setMeasurement('key', 1.0, unit: DurationSentryMeasurementUnit.hour); - - expect(sut.measurements['key']!.value, 1.0); - expect(sut.measurements['key']?.unit, DurationSentryMeasurementUnit.hour); - - await sut.finish(); - }); - - test('tracer does not allow setting measurement if finished', () async { - final sut = fixture.getSut(); - - await sut.finish(); - - sut.setMeasurement('key', 1.0); - - expect(sut.measurements.isEmpty, true); - }); - - test('setMeasurement sets a measurement', () async { - final sut = fixture.getSut(); - sut.setMeasurement("test", 1); - expect(sut.measurements.containsKey("test"), true); - expect(sut.measurements["test"]!.value, 1); - }); - - test('setMeasurementFromChild does not override existing measurements', - () async { - final sut = fixture.getSut(); - sut.setMeasurement("test", 1); - sut.setMeasurementFromChild("test", 5); - expect(sut.measurements.containsKey("test"), true); - expect(sut.measurements["test"]!.value, 1); - }); - - test('hint passed to hub', () async { - final hint = Hint(); - - final sut = fixture.getSut(); - - await sut.finish(hint: hint); - - expect(fixture.hub.captureTransactionCalls.first.hint, hint); - }); - }); - - group('$SentryBaggageHeader', () { - late Fixture _fixture; - late Hub _hub; - - setUp(() async { - _fixture = Fixture(); - _hub = Hub(_fixture.options); - _hub.configureScope((scope) => scope.setUser(_fixture.user)); - - _hub.bindClient(_fixture.client); - }); - - SentryTracer getSut({SentryTracesSamplingDecision? samplingDecision}) { - final decision = samplingDecision ?? - SentryTracesSamplingDecision( - true, - sampleRate: 1.0, - sampleRand: 0.8, - ); - final _context = SentryTransactionContext( - 'name', - 'op', - transactionNameSource: SentryTransactionNameSource.custom, - samplingDecision: decision, - ); - - return SentryTracer(_context, _hub); - } - - test('returns baggage header', () { - final sut = getSut(); - final baggage = sut.toBaggageHeader(); - - expect(baggage!.name, 'baggage'); - - final newBaggage = SentryBaggage.fromHeader(baggage.value); - expect(newBaggage.get('sentry-trace_id'), sut.context.traceId.toString()); - expect(newBaggage.get('sentry-public_key'), 'public'); - expect(newBaggage.get('sentry-release'), 'release'); - expect(newBaggage.get('sentry-environment'), 'environment'); - expect(newBaggage.get('sentry-transaction'), 'name'); - expect(newBaggage.get('sentry-sample_rate'), '1'); - expect(newBaggage.getSampleRand(), 0.8); - expect(newBaggage.get('sentry-sampled'), 'true'); - }); - - test('skip transaction name if low cardinality', () { - final sut = getSut(); - sut.transactionNameSource = SentryTransactionNameSource.url; - final baggage = sut.toBaggageHeader(); - - final newBaggage = SentryBaggage.fromHeader(baggage!.value); - expect(newBaggage.get('sentry-transaction'), isNull); - }); - - test('sets transactionNameSource to source if not given', () { - final _context = SentryTransactionContext( - 'name', - 'op', - ); - - final tracer = SentryTracer(_context, _hub); - expect(tracer.transactionNameSource, SentryTransactionNameSource.custom); - }); - - test('formats the sample rate correctly', () { - final sut = getSut( - samplingDecision: SentryTracesSamplingDecision( - true, - sampleRate: 0.00000021, - )); - final baggage = sut.toBaggageHeader(); - - final newBaggage = SentryBaggage.fromHeader(baggage!.value); - expect(newBaggage.get('sentry-sample_rate'), '0.00000021'); - }); - }); - - group('$SentryTraceContextHeader', () { - late Fixture _fixture; - late Hub _hub; - - setUp(() async { - _fixture = Fixture(); - _hub = Hub(_fixture.options); - _hub.configureScope((scope) => scope.setUser(_fixture.user)); - - _hub.bindClient(_fixture.client); - }); - - SentryTracer getSut({SentryTracesSamplingDecision? samplingDecision}) { - final decision = samplingDecision ?? - SentryTracesSamplingDecision( - true, - sampleRate: 1.0, - ); - final _context = SentryTransactionContext( - 'name', - 'op', - transactionNameSource: SentryTransactionNameSource.custom, - samplingDecision: decision, - ); - - return SentryTracer(_context, _hub); - } - - test('returns trace context header', () { - final sut = getSut(); - final context = sut.traceContext(); - - expect(context!.traceId, sut.context.traceId); - expect(context.publicKey, 'public'); - expect(context.release, 'release'); - expect(context.environment, 'environment'); - expect(context.transaction, 'name'); - expect(context.sampleRate, '1'); - expect(context.sampled, 'true'); - }); - }); -} - -class Fixture { - final options = defaultTestOptions() - ..release = 'release' - ..environment = 'environment'; - - final client = MockSentryClient(); - - final user = SentryUser( - id: 'id', - ); - - final hub = MockHub(); - - SentryTracer getSut({ - bool? sampled = true, - bool waitForChildren = false, - bool trimEnd = false, - Duration? autoFinishAfter, - }) { - final context = SentryTransactionContext( - 'name', - 'op', - samplingDecision: SentryTracesSamplingDecision(sampled!), - origin: 'manual', - ); - return SentryTracer( - context, - hub, - waitForChildren: waitForChildren, - autoFinishAfter: autoFinishAfter, - trimEnd: trimEnd, - ); - } -} diff --git a/dart/test/sentry_traces_sampler_test.dart b/dart/test/sentry_traces_sampler_test.dart deleted file mode 100644 index cb2d96ecf1..0000000000 --- a/dart/test/sentry_traces_sampler_test.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/sentry_traces_sampler.dart'; -import 'package:test/test.dart'; - -import 'test_utils.dart'; - -void main() { - late Fixture fixture; - const _sampleRand = 1.0; - - setUp(() { - fixture = Fixture(); - }); - - test('transactionContext has sampled', () { - final sut = fixture.getSut(); - - final trContext = SentryTransactionContext( - 'name', - 'op', - samplingDecision: SentryTracesSamplingDecision(true), - ); - final context = SentrySamplingContext(trContext, {}); - - expect(sut.sample(context, _sampleRand).sampled, true); - }); - - test('options has sampler', () { - double? sampler(SentrySamplingContext samplingContext) { - return 1.0; - } - - final sut = fixture.getSut( - tracesSampleRate: null, - tracesSampler: sampler, - ); - - final trContext = SentryTransactionContext( - 'name', - 'op', - ); - final context = SentrySamplingContext(trContext, {}); - - expect(sut.sample(context, _sampleRand).sampled, true); - }); - - test('transactionContext has parentSampled', () { - final sut = fixture.getSut(tracesSampleRate: null); - - final trContext = SentryTransactionContext( - 'name', - 'op', - parentSamplingDecision: SentryTracesSamplingDecision(true), - ); - final context = SentrySamplingContext(trContext, {}); - - expect(sut.sample(context, _sampleRand).sampled, true); - }); - - test('options has rate 1.0', () { - final sut = fixture.getSut(); - - final trContext = SentryTransactionContext( - 'name', - 'op', - ); - final context = SentrySamplingContext(trContext, {}); - - expect(sut.sample(context, _sampleRand).sampled, true); - }); - - test('options has rate 0.0', () { - final sut = fixture.getSut(tracesSampleRate: 0.0); - - final trContext = SentryTransactionContext( - 'name', - 'op', - ); - final context = SentrySamplingContext(trContext, {}); - - expect(sut.sample(context, _sampleRand).sampled, false); - }); - - test('does not sample if tracesSampleRate and tracesSampleRate are null', () { - final sut = fixture.getSut(tracesSampleRate: null, tracesSampler: null); - - final trContext = SentryTransactionContext( - 'name', - 'op', - ); - final context = SentrySamplingContext(trContext, {}); - final samplingDecision = sut.sample(context, _sampleRand); - - expect(samplingDecision.sampleRate, isNull); - expect(samplingDecision.sampleRand, isNull); - expect(samplingDecision.sampled, false); - }); - - test('tracesSampler exception is handled', () { - fixture.options.automatedTestMode = false; - final sut = fixture.getSut(debug: true); - - final exception = Exception("tracesSampler exception"); - double? sampler(SentrySamplingContext samplingContext) { - throw exception; - } - - fixture.options.tracesSampler = sampler; - - final trContext = SentryTransactionContext( - 'name', - 'op', - ); - final context = SentrySamplingContext(trContext, {}); - sut.sample(context, _sampleRand); - - expect(fixture.loggedException, exception); - expect(fixture.loggedLevel, SentryLevel.error); - }); -} - -class Fixture { - final options = defaultTestOptions(); - - SentryLevel? loggedLevel; - Object? loggedException; - - SentryTracesSampler getSut({ - double? tracesSampleRate = 1.0, - TracesSamplerCallback? tracesSampler, - bool debug = false, - }) { - options.tracesSampleRate = tracesSampleRate; - options.tracesSampler = tracesSampler; - options.debug = debug; - options.log = mockLogger; - return SentryTracesSampler(options); - } - - void mockLogger( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - loggedLevel = level; - loggedException = exception; - } -} diff --git a/dart/test/sentry_transaction_context_test.dart b/dart/test/sentry_transaction_context_test.dart deleted file mode 100644 index 09330d0afd..0000000000 --- a/dart/test/sentry_transaction_context_test.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - final _traceId = SentryId.newId(); - final _spanId = SpanId.newId(); - - SentryTransactionContext getSentryTransactionContext({ - bool? sampled, - SentryTransactionNameSource? transactionNameSource, - SentryBaggage? baggage, - }) { - final header = SentryTraceHeader(_traceId, _spanId, sampled: sampled); - return SentryTransactionContext.fromSentryTrace( - 'name', - 'operation', - header, - transactionNameSource: transactionNameSource, - baggage: baggage, - ); - } - - test('parent span id is set from header', () { - final context = getSentryTransactionContext(); - - expect(context.parentSpanId, _spanId); - }); - - test('trace id is set from header', () { - final context = getSentryTransactionContext(); - - expect(context.traceId, _traceId); - }); - - test('parent sampled is set from header', () { - final context = getSentryTransactionContext(sampled: true); - - expect(context.parentSamplingDecision?.sampled, true); - }); - - test('transactionNameSource is custom by default', () { - final context = getSentryTransactionContext(sampled: true); - - expect(context.transactionNameSource, SentryTransactionNameSource.custom); - }); - - test('transactionNameSource sets the given value', () { - final context = getSentryTransactionContext( - sampled: true, - transactionNameSource: SentryTransactionNameSource.component, - ); - - expect( - context.transactionNameSource, SentryTransactionNameSource.component); - }); - - test('sets sample rate if baggage is given', () { - final baggage = SentryBaggage({}); - final id = SentryId.newId().toString(); - baggage.setTraceId(id); - baggage.setPublicKey('publicKey'); - baggage.setSampleRate('1.0'); - final context = getSentryTransactionContext( - sampled: true, - baggage: baggage, - ); - - expect(context.parentSamplingDecision?.sampleRate, 1.0); - }); - - test('origin set to manual', () { - final context = getSentryTransactionContext(); - - expect(context.origin, SentryTraceOrigins.manual); - }); -} diff --git a/dart/test/sentry_transaction_test.dart b/dart/test/sentry_transaction_test.dart deleted file mode 100644 index 14245aed5a..0000000000 --- a/dart/test/sentry_transaction_test.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:test/test.dart'; - -import 'mocks/mock_hub.dart'; -import 'test_utils.dart'; - -void main() { - final fixture = Fixture(); - - SentryTracer _createTracer({ - bool? sampled = true, - Hub? hub, - }) { - final context = SentryTransactionContext( - 'name', - 'op', - samplingDecision: SentryTracesSamplingDecision(sampled!), - transactionNameSource: SentryTransactionNameSource.component, - ); - return SentryTracer(context, hub ?? MockHub()); - } - - test('toJson serializes', () async { - final tracer = _createTracer(hub: fixture.hub); - - final child = tracer.startChild('child'); - await child.finish(); - await tracer.finish(); - - final sut = fixture.getSut(tracer); - final map = sut.toJson(); - - expect(map['type'], 'transaction'); - expect(map['start_timestamp'], isNotNull); - expect(map['spans'], isNotNull); - expect(map['transaction_info']['source'], 'component'); - }); - - test('returns finished if it is', () async { - final tracer = _createTracer(); - final child = tracer.startChild('child'); - await child.finish(); - await tracer.finish(); - - final sut = fixture.getSut(tracer); - - expect(sut.finished, true); - }); - - test('returns sampled if theres context', () async { - final tracer = _createTracer(sampled: true); - final child = tracer.startChild('child'); - await child.finish(); - await tracer.finish(); - - final sut = fixture.getSut(tracer); - - expect(sut.sampled, true); - }); - - test('returns contexts.trace.data if data is set', () async { - final tracer = _createTracer(sampled: true); - tracer.setData('key', 'value'); - final child = tracer.startChild('child'); - await child.finish(); - await tracer.finish(); - - final sut = fixture.getSut(tracer); - - expect(sut.contexts.trace!.data, {'key': 'value'}); - }); - - test('returns null contexts.trace.data if data is not set', () async { - final tracer = _createTracer(sampled: true); - final child = tracer.startChild('child'); - await child.finish(); - await tracer.finish(); - - final sut = fixture.getSut(tracer); - - expect(sut.contexts.trace!.data, isNull); - }); - - test('returns sampled false if not sampled', () async { - final tracer = _createTracer(sampled: false); - final child = tracer.startChild('child'); - await child.finish(); - await tracer.finish(); - - final sut = fixture.getSut(tracer); - - expect(sut.sampled, false); - }); -} - -class Fixture { - final SentryOptions options = defaultTestOptions(); - late final Hub hub = Hub(options); - - SentryTransaction getSut(SentryTracer tracer) { - return SentryTransaction(tracer); - } -} diff --git a/dart/test/stack_trace_test.dart b/dart/test/stack_trace_test.dart deleted file mode 100644 index ebb1b20613..0000000000 --- a/dart/test/stack_trace_test.dart +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:sentry/src/origin.dart'; -import 'package:sentry/src/platform/mock_platform.dart'; -import 'package:sentry/src/sentry_stack_trace_factory.dart'; -import 'package:stack_trace/stack_trace.dart'; -import 'package:test/test.dart'; - -import 'test_utils.dart'; - -void main() { - group('encodeStackTraceFrame', () { - test('marks dart: frames as not app frames', () { - final frame = Frame(Uri.parse('dart:core'), 1, 2, 'buzz'); - - expect( - Fixture().getSut().encodeStackTraceFrame(frame)!.toJson(), - { - 'abs_path': '${eventOrigin}dart:core', - 'function': 'buzz', - 'lineno': 1, - 'colno': 2, - 'in_app': false, - 'filename': 'core', - 'platform': 'dart', - }, - ); - }); - - test('cleans absolute paths', () { - final frame = Frame(Uri.parse('file://foo/bar/baz.dart'), 1, 2, 'buzz'); - expect( - Fixture().getSut().encodeStackTraceFrame(frame)!.toJson()['abs_path'], - '${eventOrigin}baz.dart', - ); - }); - - test('send exception package', () { - final frame = Frame(Uri.parse('package:toolkit/baz.dart'), 1, 2, 'buzz'); - final encodedFrame = Fixture() - .getSut(inAppExcludes: ['toolkit']).encodeStackTraceFrame(frame)!; - expect(encodedFrame.package, 'toolkit'); - }); - - test('apply inAppExcludes', () { - final frame = Frame(Uri.parse('package:toolkit/baz.dart'), 1, 2, 'buzz'); - final serializedFrame = Fixture() - .getSut(inAppExcludes: ['toolkit']).encodeStackTraceFrame(frame)!; - - expect(serializedFrame.inApp, false); - }); - - test('apply inAppIncludes', () { - final frame = Frame(Uri.parse('package:toolkit/baz.dart'), 1, 2, 'buzz'); - final serializedFrame = Fixture() - .getSut(inAppIncludes: ['toolkit']).encodeStackTraceFrame(frame)!; - - expect(serializedFrame.inApp, true); - }); - - test('flutter package is not inApp', () { - final frame = - Frame(Uri.parse('package:flutter/material.dart'), 1, 2, 'buzz'); - final serializedFrame = Fixture().getSut().encodeStackTraceFrame(frame)!; - - expect(serializedFrame.inApp, false); - }); - - test('apply inAppIncludes with precedence', () { - final frame = Frame(Uri.parse('package:toolkit/baz.dart'), 1, 2, 'buzz'); - final serializedFrame = Fixture().getSut( - inAppExcludes: ['toolkit'], - inAppIncludes: ['toolkit']).encodeStackTraceFrame(frame)!; - - expect(serializedFrame.inApp, true); - }); - - test('uses default value from options, default = true', () { - // The following frame meets the following conditions: - // - frame.uri.scheme is empty - // - frame.package is null - // These conditions triggers the default value being used - final frame = Frame.parseVM('#0 Foo (async/future.dart:0:0)'); - - // default is true - final serializedFrame = Fixture() - .getSut(considerInAppFramesByDefault: true) - .encodeStackTraceFrame(frame)!; - - expect(serializedFrame.inApp, true); - }); - - test('uses default value from options, default = false', () { - // The following frame meets the following conditions: - // - frame.uri.scheme is empty - // - frame.package is null - // These conditions triggers the default value being used - final frame = Frame.parseVM('#0 Foo (async/future.dart:0:0)'); - - // default is true - final serializedFrame = Fixture() - .getSut(considerInAppFramesByDefault: false) - .encodeStackTraceFrame(frame)!; - - expect(serializedFrame.inApp, false); - }); - - test('adds module for package frames', () { - final frame = Frame( - Uri.parse( - 'package:app_name/features/login/ui/view_model/login_view_model.dart'), - 1, - 2, - 'buzz', - ); - - final sentryStackFrame = Fixture() - .getSut(includeModuleInStackTrace: true) - .encodeStackTraceFrame(frame)!; - - expect(sentryStackFrame.module, 'app_name/features/login/ui/view_model'); - }); - - test( - 'does not add module for package frames when includeModuleInStackTrace is false', - () { - final frame = Frame( - Uri.parse( - 'package:app_name/features/login/ui/view_model/login_view_model.dart'), - 1, - 2, - 'buzz', - ); - - final sentryStackFrame = Fixture() - .getSut(includeModuleInStackTrace: false) - .encodeStackTraceFrame(frame)!; - - expect(sentryStackFrame.module, null); - }); - }); - - group('encodeStackTrace', () { - test('encodes a simple stack trace', () { - final frames = - Fixture().getSut(considerInAppFramesByDefault: true).parse(''' -#0 baz (file:///pathto/test.dart:50:3) -#1 bar (file:///pathto/test.dart:46:9) - ''').frames.map((frame) => frame.toJson()); - - expect(frames, [ - { - 'abs_path': '${eventOrigin}test.dart', - 'function': 'bar', - 'lineno': 46, - 'colno': 9, - 'in_app': true, - 'filename': 'test.dart', - 'platform': 'dart', - }, - { - 'abs_path': '${eventOrigin}test.dart', - 'function': 'baz', - 'lineno': 50, - 'colno': 3, - 'in_app': true, - 'filename': 'test.dart', - 'platform': 'dart', - }, - ]); - }); - - test('obsoleted getStackFrames works as expected', () { - final sut = Fixture().getSut(considerInAppFramesByDefault: true); - final trace = ''' -#0 baz (file:///pathto/test.dart:50:3) -#1 bar (file:///pathto/test.dart:46:9) - '''; - final frames1 = sut.parse(trace).frames.map((frame) => frame.toJson()); - // ignore: deprecated_member_use_from_same_package - final frames2 = sut.getStackFrames(trace).map((frame) => frame.toJson()); - - expect(frames1, equals(frames2)); - }); - - test('encodes an asynchronous stack trace', () { - final frames = - Fixture().getSut(considerInAppFramesByDefault: true).parse(''' -#0 baz (file:///pathto/test.dart:50:3) - -#1 bar (file:///pathto/test.dart:46:9) - ''').frames.map((frame) => frame.toJson()); - - expect(frames, [ - { - 'abs_path': '${eventOrigin}test.dart', - 'function': 'bar', - 'lineno': 46, - 'colno': 9, - 'in_app': true, - 'filename': 'test.dart', - 'platform': 'dart', - }, - { - 'abs_path': '', - }, - { - 'abs_path': '${eventOrigin}test.dart', - 'function': 'baz', - 'lineno': 50, - 'colno': 3, - 'in_app': true, - 'filename': 'test.dart', - 'platform': 'dart', - }, - ]); - }); - - test('parses obfuscated stack trace', () { - final stackTraces = [ - // Older format up to Dart SDK v2.18 (Flutter v3.3) - ''' -warning: This VM has been configured to produce stack traces that violate the Dart standard. -*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** -pid: 30930, tid: 30990, name 1.ui -build_id: '5346e01103ffeed44e97094ff7bfcc19' -isolate_dso_base: 723d447000, vm_dso_base: 723d447000 -isolate_instructions: 723d452000, vm_instructions: 723d449000 - #00 abs 000000723d6346d7 virt 00000000001ed6d7 _kDartIsolateSnapshotInstructions+0x1e26d7 - #01 abs 000000723d637527 virt 00000000001f0527 _kDartIsolateSnapshotInstructions+0x1e5527 - ''', - // Newer format - ''' -*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** -pid: 19226, tid: 6103134208, name io.flutter.ui -os: macos arch: arm64 comp: no sim: no -isolate_dso_base: 10fa20000, vm_dso_base: 10fa20000 -isolate_instructions: 10fa27070, vm_instructions: 10fa21e20 - #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 - #01 abs 000000723d637527 _kDartIsolateSnapshotInstructions+0x1e5527 - ''', - ]; - - for (var traceString in stackTraces) { - final frames = Fixture() - .getSut(considerInAppFramesByDefault: true) - .parse(traceString) - .frames - .map((frame) => frame.toJson()); - - expect( - frames, - [ - { - 'platform': 'native', - 'instruction_addr': '0x000000723d637527', - }, - { - 'platform': 'native', - 'instruction_addr': '0x000000723d6346d7', - }, - ], - reason: "Failed to parse StackTrace:$traceString"); - } - }); - - test('parses normal stack trace', () { - final frames = - Fixture().getSut(considerInAppFramesByDefault: true).parse(''' -#0 asyncThrows (file:/foo/bar/main.dart:404) -#1 MainScaffold.build. (package:example/main.dart:131) -#2 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:341) - ''').frames.map((frame) => frame.toJson()); - expect(frames, [ - { - 'filename': 'platform_dispatcher.dart', - 'function': 'PlatformDispatcher._dispatchPointerDataPacket', - 'lineno': 341, - 'abs_path': '${eventOrigin}dart:ui/platform_dispatcher.dart', - 'in_app': false, - 'platform': 'dart', - }, - { - 'filename': 'main.dart', - 'package': 'example', - 'function': 'MainScaffold.build.', - 'lineno': 131, - 'abs_path': '${eventOrigin}package:example/main.dart', - 'in_app': true, - 'platform': 'dart', - }, - { - 'filename': 'main.dart', - 'function': 'asyncThrows', - 'lineno': 404, - 'abs_path': '${eventOrigin}main.dart', - 'in_app': true, - 'platform': 'dart', - } - ]); - }); - - test('remove frames if only async gap is left', () { - final frames = Fixture() - .getSut(considerInAppFramesByDefault: true) - .parse(StackTrace.fromString(''' - - ''')) - .frames - .map((frame) => frame.toJson()); - expect(frames.isEmpty, true); - }); - - test('sets platform to javascript for web and dart for non-web', () { - final frame = Frame(Uri.parse('file://foo/bar/baz.dart'), 1, 2, 'buzz'); - final fixture = Fixture(); - - // Test for web platform - fixture.options.platform = MockPlatform(isWeb: true); - final webSut = fixture.getSut(); - var webFrame = webSut.encodeStackTraceFrame(frame)!; - expect(webFrame.platform, 'javascript'); - - // Test for non-web platform - fixture.options.platform = MockPlatform(isWeb: false); - final nativeFrameBeforeSut = fixture.getSut(); - var nativeFrameBefore = - nativeFrameBeforeSut.encodeStackTraceFrame(frame)!; - expect(nativeFrameBefore.platform, 'dart'); - }); - }); -} - -class Fixture { - final options = defaultTestOptions()..platform = MockPlatform(isWeb: false); - - SentryStackTraceFactory getSut({ - List inAppIncludes = const [], - List inAppExcludes = const [], - bool considerInAppFramesByDefault = true, - bool includeModuleInStackTrace = false, - }) { - inAppIncludes.forEach(options.addInAppInclude); - inAppExcludes.forEach(options.addInAppExclude); - options.considerInAppFramesByDefault = considerInAppFramesByDefault; - options.includeModuleInStackTrace = includeModuleInStackTrace; - - return SentryStackTraceFactory(options); - } -} diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart deleted file mode 100644 index cb9fc9a479..0000000000 --- a/dart/test/test_utils.dart +++ /dev/null @@ -1,459 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; -import 'dart:convert'; - -import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/platform/platform.dart'; -import 'package:sentry/src/version.dart'; -import 'package:test/test.dart'; - -const String testDsn = 'https://public:secret@sentry.example.com/1'; -const String _testDsnWithoutSecret = 'https://public@sentry.example.com/1'; -const String _testDsnWithPath = - 'https://public:secret@sentry.example.com/path/1'; -const String _testDsnWithPort = - 'https://public:secret@sentry.example.com:8888/1'; - -SentryOptions defaultTestOptions( - {Platform? platform, RuntimeChecker? checker}) { - return SentryOptions(dsn: testDsn, platform: platform, checker: checker) - ..automatedTestMode = true; -} - -void testHeaders( - Map? headers, - ClockProvider fakeClockProvider, { - String? sdkName, - bool withUserAgent = true, - bool compressPayload = true, - bool withSecret = true, -}) { - final expectedHeaders = { - 'Content-Type': 'application/x-sentry-envelope', - 'X-Sentry-Auth': 'Sentry sentry_version=7, ' - 'sentry_client=$sdkName/$sdkVersion, ' - 'sentry_key=public' - }; - - if (withSecret) { - expectedHeaders['X-Sentry-Auth'] = - '${expectedHeaders['X-Sentry-Auth']!}, sentry_secret=secret'; - } - - if (withUserAgent) { - expectedHeaders['User-Agent'] = '$sdkName/$sdkVersion'; - } - - if (compressPayload) { - expectedHeaders['Content-Encoding'] = 'gzip'; - } - - expect(headers, expectedHeaders); -} - -Future testCaptureException( - bool compressPayload, - Codec, List?>? gzip, - bool isWeb, -) async { - final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - - Uri? postUri; - Map? headers; - List? body; - final httpMock = MockClient((http.Request request) async { - if (request.method == 'POST') { - postUri = request.url; - headers = request.headers; - body = request.bodyBytes; - return http.Response('{"id": "test-event-id"}', 200); - } - fail('Unexpected request on ${request.method} ${request.url} in HttpMock'); - }); - - final options = defaultTestOptions() - ..compressPayload = compressPayload - ..clock = fakeClockProvider - ..httpClient = httpMock - ..serverName = 'test.server.com' - ..release = '1.2.3' - ..environment = 'staging'; - - var sentryId = SentryId.empty(); - final client = SentryClient(options); - - try { - throw ArgumentError('Test error'); - } catch (error, stackTrace) { - sentryId = await client.captureException(error, stackTrace: stackTrace); - expect('$sentryId', 'testeventid'); - } - - final dsn = Dsn.parse(options.dsn!); - expect(postUri, dsn.postUri); - - testHeaders( - headers, - fakeClockProvider, - compressPayload: compressPayload, - withUserAgent: !isWeb, - sdkName: sdkName(isWeb), - ); - - String envelopeData; - if (compressPayload) { - envelopeData = utf8.decode(gzip!.decode(body)); - } else { - envelopeData = utf8.decode(body!); - } - final eventJson = envelopeData.split('\n').last; - final data = json.decode(eventJson) as Map?; - - // so we assert the generated and returned id - data!['event_id'] = sentryId.toString(); - - final stacktrace = data['exception']['values'].first['stacktrace']; - - expect(stacktrace['frames'], const TypeMatcher()); - expect(stacktrace['frames'], isNotEmpty); - - final topFrame = - (stacktrace['frames'] as Iterable).last as Map; - if (topFrame['function'].contains('browser_test.dart.wasm')) { - // TODO stacktrace parsing for wasm is not implemented yet - // {filename: unparsed, function: at testCaptureException (http://localhost:59959/9R3KYfjvkWCySr4h2hI0pVO7PqmPFeE6/test/sentry_browser_test.dart.browser_test.dart.wasm:wasm-function[1007]:0x4bc18), abs_path: http://localhost:59959/unparsed, in_app: true} - return; - } - expect( - topFrame.keys, - [ - 'filename', - 'function', - 'lineno', - 'colno', - 'abs_path', - 'in_app', - 'platform' - ], - ); - - if (isWeb) { - // can't test the full url - // the localhost port can change - final absPathUri = Uri.parse(topFrame['abs_path'] as String); - expect(absPathUri.host, 'localhost'); - expect( - absPathUri.path, - anyOf([ - '/sentry_browser_test.dart.browser_test.dart.js', - '/sentry_browser_test.dart.browser_test.dart.wasm' - ])); - - expect( - topFrame['filename'], - anyOf([ - 'sentry_browser_test.dart.browser_test.dart.js', - 'sentry_browser_test.dart.browser_test.dart.wasm' - ])); - expect( - topFrame['function'], - anyOf([ - 'Object.wrapException', - 'testCaptureException', - 'module0.testCaptureException' - ])); - - expect(data['event_id'], sentryId.toString()); - expect(data['timestamp'], '2017-01-02T00:00:00.000Z'); - expect(data['platform'], 'javascript'); - expect(data['sdk'], { - 'version': sdkVersion, - 'name': sdkName(isWeb), - 'packages': [ - {'name': 'pub:sentry', 'version': sdkVersion} - ] - }); - expect(data['server_name'], 'test.server.com'); - expect(data['release'], '1.2.3'); - expect(data['environment'], 'staging'); - - expect(data['exception']['values'].first['type'], 'ArgumentError'); - expect(data['exception']['values'].first['value'], - 'Invalid argument(s): Test error'); - } else { - expect(topFrame['abs_path'], 'test_utils.dart'); - expect(topFrame['filename'], 'test_utils.dart'); - expect(topFrame['function'], 'testCaptureException'); - - expect(data['event_id'], sentryId.toString()); - expect(data['timestamp'], '2017-01-02T00:00:00.000Z'); - expect(data['platform'], 'other'); - expect(data['sdk'], { - 'version': sdkVersion, - 'name': 'sentry.dart', - 'packages': [ - {'name': 'pub:sentry', 'version': sdkVersion} - ] - }); - expect(data['server_name'], 'test.server.com'); - expect(data['release'], '1.2.3'); - expect(data['environment'], 'staging'); - expect(data['exception']['values'].first['type'], 'ArgumentError'); - expect(data['exception']['values'].first['value'], - 'Invalid argument(s): Test error'); - } - - expect(topFrame['lineno'], greaterThan(0)); - expect(topFrame['in_app'], true); - - client.close(); -} - -void runTest({Codec, List?>? gzip, bool isWeb = false}) { - test('can parse DSN', () async { - final options = defaultTestOptions(); - final client = SentryClient(options); - - final dsn = Dsn.parse(options.dsn!); - - expect(dsn.uri, Uri.parse(testDsn)); - expect( - dsn.postUri, - Uri.parse('https://sentry.example.com/api/1/envelope/'), - ); - expect(dsn.publicKey, 'public'); - expect(dsn.secretKey, 'secret'); - expect(dsn.projectId, '1'); - client.close(); - }); - - test('can parse DSN without secret', () async { - final options = defaultTestOptions()..dsn = _testDsnWithoutSecret; - final client = SentryClient(options); - - final dsn = Dsn.parse(options.dsn!); - - expect(dsn.uri, Uri.parse(_testDsnWithoutSecret)); - expect( - dsn.postUri, - Uri.parse('https://sentry.example.com/api/1/envelope/'), - ); - expect(dsn.publicKey, 'public'); - expect(dsn.secretKey, null); - expect(dsn.projectId, '1'); - client.close(); - }); - - test('can parse DSN with path', () async { - final options = defaultTestOptions()..dsn = _testDsnWithPath; - final client = SentryClient(options); - - final dsn = Dsn.parse(options.dsn!); - - expect(dsn.uri, Uri.parse(_testDsnWithPath)); - expect( - dsn.postUri, - Uri.parse('https://sentry.example.com/path/api/1/envelope/'), - ); - expect(dsn.publicKey, 'public'); - expect(dsn.secretKey, 'secret'); - expect(dsn.projectId, '1'); - client.close(); - }); - test('can parse DSN with port', () async { - final options = defaultTestOptions()..dsn = _testDsnWithPort; - final client = SentryClient(options); - - final dsn = Dsn.parse(options.dsn!); - - expect(dsn.uri, Uri.parse(_testDsnWithPort)); - expect( - dsn.postUri, - Uri.parse('https://sentry.example.com:8888/api/1/envelope/'), - ); - expect(dsn.publicKey, 'public'); - expect(dsn.secretKey, 'secret'); - expect(dsn.projectId, '1'); - client.close(); - }); - test('sends client auth header without secret', () async { - final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - - Map? headers; - - final httpMock = MockClient((http.Request request) async { - if (request.method == 'POST') { - headers = request.headers; - return http.Response('{"id": "testeventid"}', 200); - } - fail( - 'Unexpected request on ${request.method} ${request.url} in HttpMock', - ); - }); - - final client = SentryClient( - defaultTestOptions() - ..dsn = _testDsnWithoutSecret - ..httpClient = httpMock - ..clock = fakeClockProvider - ..compressPayload = false - ..serverName = 'test.server.com' - ..release = '1.2.3' - ..environment = 'staging', - ); - - try { - throw ArgumentError('Test error'); - } catch (error, stackTrace) { - final sentryId = - await client.captureException(error, stackTrace: stackTrace); - expect('$sentryId', 'testeventid'); - } - - testHeaders( - headers, - fakeClockProvider, - withUserAgent: !isWeb, - compressPayload: false, - withSecret: false, - sdkName: sdkName(isWeb), - ); - - client.close(); - }); - - test('sends an exception report (compressed)', () async { - await testCaptureException(true, gzip, isWeb); - }, onPlatform: { - 'browser': const Skip(), - }); - - test('sends an exception report (uncompressed)', () async { - await testCaptureException(false, gzip, isWeb); - }); - - test('reads error message from the x-sentry-error header', () async { - final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - - final httpMock = MockClient((http.Request request) async { - if (request.method == 'POST') { - return http.Response('', 401, headers: { - 'x-sentry-error': 'Invalid api key', - }); - } - fail( - 'Unexpected request on ${request.method} ${request.url} in HttpMock', - ); - }); - - final client = SentryClient( - defaultTestOptions() - ..httpClient = httpMock - ..clock = fakeClockProvider - ..compressPayload = false - ..serverName = 'test.server.com' - ..release = '1.2.3' - ..environment = 'staging', - ); - - try { - throw ArgumentError('Test error'); - } catch (error, stackTrace) { - final sentryId = - await client.captureException(error, stackTrace: stackTrace); - expect('$sentryId', '00000000000000000000000000000000'); - } - - client.close(); - }); - - test('$SentryEvent user overrides client', () async { - final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - - String? loggedUserId; // used to find out what user context was sent - final httpMock = MockClient((http.Request request) async { - if (request.method == 'POST') { - final bodyData = request.bodyBytes; - final decoded = const Utf8Codec().decode(bodyData); - final eventJson = decoded.split('\n').last; - final dynamic decodedJson = json.decode(eventJson); - - loggedUserId = decodedJson['user']['id'] as String?; - return http.Response( - '', - 401, - headers: { - 'x-sentry-error': 'Invalid api key', - }, - ); - } - fail( - 'Unexpected request on ${request.method} ${request.url} in HttpMock', - ); - }); - - final clientUser = SentryUser( - id: 'client_user', - username: 'username', - email: 'email@email.com', - ipAddress: '127.0.0.1', - ); - final eventUser = SentryUser( - id: 'event_user', - username: 'username', - email: 'email@email.com', - ipAddress: '127.0.0.1', - data: {'foo': 'bar'}, - ); - - final options = defaultTestOptions() - ..httpClient = httpMock - ..clock = fakeClockProvider - ..compressPayload = false - ..serverName = 'test.server.com' - ..release = '1.2.3' - ..environment = 'staging' - ..sendClientReports = false; - - final client = SentryClient(options); - - try { - throw ArgumentError('Test error'); - } catch (error) { - final eventWithoutContext = SentryEvent( - eventId: SentryId.empty(), - throwable: error, - ); - final eventWithContext = SentryEvent( - eventId: SentryId.empty(), - throwable: error, - user: eventUser, - ); - - final scope = Scope(options); - await scope.setUser(clientUser); - - await client.captureEvent( - eventWithoutContext, - scope: scope, - ); - expect(loggedUserId, clientUser.id); - - final secondScope = Scope(options); - await secondScope.setUser(clientUser); - - await client.captureEvent( - eventWithContext, - scope: secondScope, - ); - expect(loggedUserId, eventUser.id); - } - - client.close(); - }); -} diff --git a/dart/test/transport/client_report_transport_test.dart b/dart/test/transport/client_report_transport_test.dart deleted file mode 100644 index df9447c768..0000000000 --- a/dart/test/transport/client_report_transport_test.dart +++ /dev/null @@ -1,169 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/client_reports/client_report.dart'; -import 'package:sentry/src/client_reports/discard_reason.dart'; -import 'package:sentry/src/client_reports/discarded_event.dart'; -import 'package:sentry/src/sentry_item_type.dart'; -import 'package:sentry/src/transport/client_report_transport.dart'; -import 'package:sentry/src/transport/data_category.dart'; -import 'package:sentry/src/transport/rate_limiter.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; -import '../mocks/mock_client_report_recorder.dart'; -import '../mocks/mock_envelope.dart'; -import '../mocks/mock_transport.dart'; -import '../test_utils.dart'; - -void main() { - group('filter', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('filter called', () async { - final mockRateLimiter = MockRateLimiter(); - final sut = fixture.getSut(rateLimiter: mockRateLimiter); - - final envelope = MockEnvelope(); - envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; - - await sut.send(envelope); - - expect(mockRateLimiter.envelopeToFilter, envelope); - expect(fixture.mockTransport.envelopes.first, envelope); - }); - - test('send nothing when filtered event null', () async { - final mockRateLimiter = MockRateLimiter()..filterReturnsNull = true; - final sut = fixture.getSut(rateLimiter: mockRateLimiter); - - final envelope = MockEnvelope(); - envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; - - final eventId = await sut.send(envelope); - - expect(eventId, SentryId.empty()); - expect(fixture.mockTransport.called(0), true); - }); - }); - - group('client reports', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('send calls flush', () async { - final sut = fixture.getSut(); - - final envelope = MockEnvelope(); - envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; - - await sut.send(envelope); - - expect(fixture.recorder.flushCalled, true); - }); - - test('send adds client report', () async { - final clientReport = ClientReport( - DateTime(0), - [DiscardedEvent(DiscardReason.rateLimitBackoff, DataCategory.error, 1)], - ); - fixture.recorder.clientReport = clientReport; - - final sut = fixture.getSut(); - - final envelope = MockEnvelope(); - envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; - - await sut.send(envelope); - - expect(envelope.clientReport, clientReport); - }); - }); - - group('client report only event', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('send after filtering out 10 times and client report', () async { - final clientReport = ClientReport( - DateTime(0), - [DiscardedEvent(DiscardReason.rateLimitBackoff, DataCategory.error, 1)], - ); - fixture.recorder.clientReport = clientReport; - - final mockRateLimiter = MockRateLimiter()..filterReturnsNull = true; - - final sut = fixture.getSut(rateLimiter: mockRateLimiter); - - final envelope = MockEnvelope(); - envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; - - for (int i = 0; i < 10; i++) { - await sut.send(envelope); - } - - expect(fixture.mockTransport.called(1), true); - - final sentEnvelope = fixture.mockTransport.envelopes.first; - expect(sentEnvelope.items.length, 1); - expect(sentEnvelope.items[0].header.type, SentryItemType.clientReport); - }); - - test('filter out after 10 times with no client reports', () async { - final mockRateLimiter = MockRateLimiter()..filterReturnsNull = true; - - final sut = fixture.getSut(rateLimiter: mockRateLimiter); - - final envelope = MockEnvelope(); - envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; - - for (int i = 0; i < 10; i++) { - await sut.send(envelope); - } - - expect(fixture.mockTransport.called(0), true); - }); - - test('reset counter', () async { - final clientReport = ClientReport( - DateTime(0), - [DiscardedEvent(DiscardReason.rateLimitBackoff, DataCategory.error, 1)], - ); - fixture.recorder.clientReport = clientReport; - - final mockRateLimiter = MockRateLimiter()..filterReturnsNull = true; - - final sut = fixture.getSut(rateLimiter: mockRateLimiter); - - final envelope = MockEnvelope(); - envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; - - for (int i = 0; i < 20; i++) { - await sut.send(envelope); - } - - expect(fixture.mockTransport.called(2), true); - }); - }); -} - -class Fixture { - final options = defaultTestOptions(); - - late var recorder = MockClientReportRecorder(); - late var mockTransport = MockTransport(); - - ClientReportTransport getSut({RateLimiter? rateLimiter}) { - mockTransport.parseFromEnvelope = false; - options.recorder = recorder; - return ClientReportTransport(rateLimiter, options, mockTransport); - } -} diff --git a/dart/test/transport/http_transport_test.dart b/dart/test/transport/http_transport_test.dart deleted file mode 100644 index 0c0307a4b5..0000000000 --- a/dart/test/transport/http_transport_test.dart +++ /dev/null @@ -1,306 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/client_reports/discard_reason.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:sentry/src/transport/data_category.dart'; -import 'package:sentry/src/transport/http_transport.dart'; -import 'package:sentry/src/transport/rate_limiter.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; -import '../mocks/mock_client_report_recorder.dart'; -import '../mocks/mock_hub.dart'; -import '../test_utils.dart'; - -void main() { - group('send', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('event with http client', () async { - List? body; - - final httpMock = MockClient((http.Request request) async { - body = request.bodyBytes; - return http.Response('{}', 200); - }); - - fixture.options.compressPayload = false; - final mockRateLimiter = MockRateLimiter(); - - final sut = fixture.getSut(httpMock, mockRateLimiter); - - final sentryEvent = SentryEvent(); - final envelope = SentryEnvelope.fromEvent( - sentryEvent, - fixture.options.sdk, - dsn: fixture.options.dsn, - ); - await sut.send(envelope); - - final envelopeData = []; - await envelope - .envelopeStream(fixture.options) - .forEach(envelopeData.addAll); - - expect(body, envelopeData); - }); - }); - - group('updateRetryAfterLimits', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('retryAfterHeader', () async { - final httpMock = MockClient((http.Request request) async { - return http.Response('{}', 429, headers: {'Retry-After': '1'}); - }); - final mockRateLimiter = MockRateLimiter(); - final sut = fixture.getSut(httpMock, mockRateLimiter); - - final sentryEvent = SentryEvent(); - final envelope = SentryEnvelope.fromEvent( - sentryEvent, - fixture.options.sdk, - dsn: fixture.options.dsn, - ); - - mockRateLimiter.filter(envelope); - - await sut.send(envelope); - - expect(mockRateLimiter.envelopeToFilter?.header.eventId, - sentryEvent.eventId); - - expect(mockRateLimiter.errorCode, 429); - expect(mockRateLimiter.retryAfterHeader, '1'); - expect(mockRateLimiter.sentryRateLimitHeader, isNull); - - expect(fixture.loggedLevel, SentryLevel.warning); - expect( - fixture.loggedMessage, 'Rate limit reached, failed to send envelope'); - }); - - test('sentryRateLimitHeader', () async { - final httpMock = MockClient((http.Request request) async { - return http.Response('{}', 200, - headers: {'X-Sentry-Rate-Limits': 'fixture-sentryRateLimitHeader'}); - }); - final mockRateLimiter = MockRateLimiter(); - final sut = fixture.getSut(httpMock, mockRateLimiter); - - final sentryEvent = SentryEvent(); - final envelope = SentryEnvelope.fromEvent( - sentryEvent, - fixture.options.sdk, - dsn: fixture.options.dsn, - ); - await sut.send(envelope); - - expect(mockRateLimiter.errorCode, 200); - expect(mockRateLimiter.retryAfterHeader, isNull); - expect(mockRateLimiter.sentryRateLimitHeader, - 'fixture-sentryRateLimitHeader'); - }); - }); - - group('sent_at', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('capture envelope sets sent_at in header', () async { - final sentryEvent = SentryEvent(); - final envelope = SentryEnvelope.fromEvent( - sentryEvent, - fixture.options.sdk, - dsn: fixture.options.dsn, - ); - - final httpMock = MockClient((http.Request request) async { - return http.Response('{}', 200); - }); - final sut = fixture.getSut(httpMock, MockRateLimiter()); - await sut.send(envelope); - - expect(envelope.header.sentAt, DateTime.utc(2019)); - }); - }); - - group('client reports', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('does records lost event for error >= 400', () async { - final httpMock = MockClient((http.Request request) async { - return http.Response('{}', 400); - }); - final sut = fixture.getSut(httpMock, MockRateLimiter()); - - final sentryEvent = SentryEvent(); - final envelope = SentryEnvelope.fromEvent( - sentryEvent, - fixture.options.sdk, - dsn: fixture.options.dsn, - ); - await sut.send(envelope); - - expect(fixture.clientReportRecorder.discardedEvents.first.reason, - DiscardReason.networkError); - expect(fixture.clientReportRecorder.discardedEvents.first.category, - DataCategory.error); - }); - - test('does records lost transaction and span for error >= 400', () async { - final httpMock = MockClient((http.Request request) async { - return http.Response('{}', 400); - }); - final sut = fixture.getSut(httpMock, MockRateLimiter()); - - final transaction = fixture.getTransaction(); - transaction.tracer.startChild('child1'); - transaction.tracer.startChild('child2'); - final envelope = SentryEnvelope.fromTransaction( - transaction, - fixture.options.sdk, - dsn: fixture.options.dsn, - ); - await sut.send(envelope); - - final transactionDiscardedEvent = fixture - .clientReportRecorder.discardedEvents - .firstWhereOrNull((element) => - element.category == DataCategory.transaction && - element.reason == DiscardReason.networkError); - - final spanDiscardedEvent = fixture.clientReportRecorder.discardedEvents - .firstWhereOrNull((element) => - element.category == DataCategory.span && - element.reason == DiscardReason.networkError); - - expect(transactionDiscardedEvent, isNotNull); - expect(spanDiscardedEvent, isNotNull); - expect(spanDiscardedEvent!.quantity, 3); - }); - - test('does record lost feedback for error >= 400', () async { - final httpMock = MockClient((http.Request request) async { - return http.Response('{}', 400); - }); - final sut = fixture.getSut(httpMock, MockRateLimiter()); - - final feedback = SentryFeedback(message: 'fixture-message'); - final feedbackEvent = SentryEvent( - type: 'feedback', - contexts: Contexts(feedback: feedback), - level: SentryLevel.info, - ); - final envelope = SentryEnvelope.fromEvent( - feedbackEvent, - fixture.options.sdk, - dsn: fixture.options.dsn, - ); - await sut.send(envelope); - - expect(fixture.clientReportRecorder.discardedEvents.first.reason, - DiscardReason.networkError); - expect(fixture.clientReportRecorder.discardedEvents.first.category, - DataCategory.feedback); - }); - - test('does not record lost event for error 429', () async { - final httpMock = MockClient((http.Request request) async { - return http.Response('{}', 429); - }); - final sut = fixture.getSut(httpMock, MockRateLimiter()); - - final sentryEvent = SentryEvent(); - final envelope = SentryEnvelope.fromEvent( - sentryEvent, - fixture.options.sdk, - dsn: fixture.options.dsn, - ); - await sut.send(envelope); - - expect(fixture.clientReportRecorder.discardedEvents.isEmpty, isTrue); - - expect(fixture.loggedLevel, SentryLevel.warning); - expect( - fixture.loggedMessage, 'Rate limit reached, failed to send envelope'); - }); - - test('does record lost event for error >= 500', () async { - final httpMock = MockClient((http.Request request) async { - return http.Response('{}', 500); - }); - final sut = fixture.getSut(httpMock, MockRateLimiter()); - - final sentryEvent = SentryEvent(); - final envelope = SentryEnvelope.fromEvent( - sentryEvent, - fixture.options.sdk, - dsn: fixture.options.dsn, - ); - await sut.send(envelope); - - expect(fixture.clientReportRecorder.discardedEvents.first.reason, - DiscardReason.networkError); - expect(fixture.clientReportRecorder.discardedEvents.first.category, - DataCategory.error); - }); - }); -} - -class Fixture { - final options = defaultTestOptions(); - - late var clientReportRecorder = MockClientReportRecorder(); - - HttpTransport getSut(http.Client client, RateLimiter rateLimiter) { - options.debug = true; - options.log = mockLogger; - options.httpClient = client; - options.recorder = clientReportRecorder; - options.clock = () { - return DateTime.utc(2019); - }; - return HttpTransport(options, rateLimiter); - } - - SentryTransaction getTransaction() { - final context = SentryTransactionContext( - 'name', - 'op', - samplingDecision: SentryTracesSamplingDecision(true), - ); - final tracer = SentryTracer(context, MockHub()); - return SentryTransaction(tracer); - } - - SentryLevel? loggedLevel; - String? loggedMessage; - - void mockLogger( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - loggedLevel = level; - loggedMessage = message; - } -} diff --git a/dart/test/transport/spotlight_http_transport_test.dart b/dart/test/transport/spotlight_http_transport_test.dart deleted file mode 100644 index 3e9d742bb1..0000000000 --- a/dart/test/transport/spotlight_http_transport_test.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/transport/http_transport.dart'; -import 'package:sentry/src/transport/rate_limiter.dart'; -import 'package:sentry/src/transport/spotlight_http_transport.dart'; -import 'package:test/expect.dart'; -import 'package:test/scaffolding.dart'; - -import '../mocks.dart'; -import '../mocks/mock_client_report_recorder.dart'; -import '../test_utils.dart'; - -void main() { - group('send to Sentry', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test('send event to Sentry even if Spotlight fails', () async { - List? body; - - final httpMock = MockClient((http.Request request) async { - body = request.bodyBytes; - if (request.url.toString() == fixture.options.spotlight.url) { - return http.Response('{}', 500); - } - return http.Response('{}', 200); - }); - - fixture.options.compressPayload = false; - final mockRateLimiter = MockRateLimiter(); - final sut = fixture.getSut(httpMock, mockRateLimiter); - - final sentryEvent = SentryEvent(); - final envelope = SentryEnvelope.fromEvent( - sentryEvent, - fixture.options.sdk, - dsn: fixture.options.dsn, - ); - await sut.send(envelope); - - final envelopeData = []; - await envelope - .envelopeStream(fixture.options) - .forEach(envelopeData.addAll); - - expect(body, envelopeData); - }); - }); -} - -class Fixture { - final options = defaultTestOptions(); - - late var clientReportRecorder = MockClientReportRecorder(); - - Transport getSut(http.Client client, RateLimiter rateLimiter) { - options.httpClient = client; - options.recorder = clientReportRecorder; - options.clock = () { - return DateTime.utc(2019); - }; - final httpTransport = HttpTransport(options, rateLimiter); - return SpotlightHttpTransport(options, httpTransport); - } -} diff --git a/dart/test/transport/tesk_queue_test.dart b/dart/test/transport/tesk_queue_test.dart deleted file mode 100644 index dca3243f01..0000000000 --- a/dart/test/transport/tesk_queue_test.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'dart:async'; - -import 'package:sentry/src/client_reports/discard_reason.dart'; -import 'package:sentry/src/transport/data_category.dart'; -import 'package:sentry/src/transport/task_queue.dart'; -import 'package:test/test.dart'; - -import '../mocks/mock_client_report_recorder.dart'; -import '../test_utils.dart'; - -void main() { - group("called sync", () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - test("enqueue only executed `maxQueueSize` times when not awaiting", - () async { - final sut = fixture.getSut(maxQueueSize: 5); - - var completedTasks = 0; - - for (int i = 0; i < 10; i++) { - unawaited(sut.enqueue(() async { - print('Task $i'); - await Future.delayed(Duration(milliseconds: 1)); - completedTasks += 1; - return 1 + 1; - }, -1, DataCategory.error)); - } - - // This will always await the other futures, even if they are running longer, as it was scheduled after them. - print('Started waiting for first 5 tasks'); - await Future.delayed(Duration(milliseconds: 1)); - print('Stopped waiting for first 5 tasks'); - - expect(completedTasks, 5); - }); - - test("enqueue picks up tasks again after await in-between", () async { - final sut = fixture.getSut(maxQueueSize: 5); - - var completedTasks = 0; - - for (int i = 1; i <= 10; i++) { - unawaited(sut.enqueue(() async { - print('Started task $i'); - await Future.delayed(Duration(milliseconds: 1)); - print('Completed task $i'); - completedTasks += 1; - return 1 + 1; - }, -1, DataCategory.error)); - } - - print('Started waiting for first 5 tasks'); - await Future.delayed(Duration(milliseconds: 1)); - print('Stopped waiting for first 5 tasks'); - - for (int i = 6; i <= 15; i++) { - unawaited(sut.enqueue(() async { - print('Started task $i'); - await Future.delayed(Duration(milliseconds: 1)); - print('Completed task $i'); - completedTasks += 1; - return 1 + 1; - }, -1, DataCategory.error)); - } - - print('Started waiting for second 5 tasks'); - await Future.delayed(Duration(milliseconds: 5)); - print('Stopped waiting for second 5 tasks'); - - expect(completedTasks, 10); // 10 were dropped - }); - - test("enqueue executes all tasks when awaiting", () async { - final sut = fixture.getSut(maxQueueSize: 5); - - var completedTasks = 0; - - for (int i = 0; i < 10; i++) { - await sut.enqueue(() async { - print('Task $i'); - await Future.delayed(Duration(milliseconds: 1)); - completedTasks += 1; - return 1 + 1; - }, -1, DataCategory.error); - } - expect(completedTasks, 10); - }); - - test("throwing tasks still execute as expected", () async { - final sut = fixture.getSut(maxQueueSize: 5); - - var completedTasks = 0; - - for (int i = 0; i < 10; i++) { - try { - await sut.enqueue(() async { - completedTasks += 1; - throw Error(); - }, -1, DataCategory.error); - } catch (_) { - // Ignore - } - } - expect(completedTasks, 10); - }); - - test('recording dropped event when category set', () async { - final sut = fixture.getSut(maxQueueSize: 5); - - for (int i = 0; i < 10; i++) { - unawaited(sut.enqueue(() async { - print('Task $i'); - return 1 + 1; - }, -1, DataCategory.error)); - } - - // This will always await the other futures, even if they are running longer, as it was scheduled after them. - print('Started waiting for first 5 tasks'); - await Future.delayed(Duration(milliseconds: 1)); - print('Stopped waiting for first 5 tasks'); - - expect(fixture.clientReportRecorder.discardedEvents.length, 5); - for (final event in fixture.clientReportRecorder.discardedEvents) { - expect(event.reason, DiscardReason.queueOverflow); - expect(event.category, DataCategory.error); - expect(event.quantity, 1); - } - }); - }); -} - -class Fixture { - final options = defaultTestOptions(); - - late var clientReportRecorder = MockClientReportRecorder(); - - TaskQueue getSut({required int maxQueueSize}) { - return DefaultTaskQueue(maxQueueSize, options.log, clientReportRecorder); - } -} diff --git a/dart/test/unsupported_throwables_test.dart b/dart/test/unsupported_throwables_test.dart deleted file mode 100644 index 9cac063a34..0000000000 --- a/dart/test/unsupported_throwables_test.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:sentry/src/hub.dart'; -import 'package:test/expect.dart'; -import 'package:test/scaffolding.dart'; - -void main() { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - group('unsupported throwable types', () { - test('wrapped string throwable does not throw when expanding', () async { - final throwableHandler = fixture.sut; - final unsupportedThrowable = 'test throwable'; - final wrappedThrowable = - throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); - - expect(() { - fixture.expando[wrappedThrowable]; - }, returnsNormally); - }); - - test('wrapped int throwable does not throw when expanding', () async { - final throwableHandler = fixture.sut; - final unsupportedThrowable = 1; - final wrappedThrowable = - throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); - - expect(() { - fixture.expando[wrappedThrowable]; - }, returnsNormally); - }); - - test('wrapped double throwable does not throw when expanding', () async { - final throwableHandler = fixture.sut; - final unsupportedThrowable = 1.0; - final wrappedThrowable = - throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); - - expect(() { - fixture.expando[wrappedThrowable]; - }, returnsNormally); - }); - - test('wrapped bool throwable does not throw when expanding', () async { - final throwableHandler = fixture.sut; - final unsupportedThrowable = true; - final wrappedThrowable = - throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); - - expect(() { - fixture.expando[wrappedThrowable]; - }, returnsNormally); - }); - - test( - 'creating multiple instances of string wrapped exceptions accesses the same expando value', - () async { - final unsupportedThrowable = 'test throwable'; - final throwableHandler = fixture.sut; - - final first = - throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); - fixture.expando[first] = 1; - - final second = - throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); - expect(fixture.expando[second], 1); - fixture.expando[second] = 2.0; - - final third = - throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); - expect(fixture.expando[third], 2.0); - }); - }); - - group('supported throwable type', () { - test('does not wrap exception if it is a supported type', () async { - final supportedThrowable = Exception('test throwable'); - final result = fixture.sut.wrapIfUnsupportedType(supportedThrowable); - - expect(result, supportedThrowable); - }); - }); -} - -class Fixture { - final expando = Expando(); - - UnsupportedThrowablesHandler get sut => UnsupportedThrowablesHandler(); -} diff --git a/dart/test/utils/http_header_utils_test.dart b/dart/test/utils/http_header_utils_test.dart deleted file mode 100644 index 21405677ff..0000000000 --- a/dart/test/utils/http_header_utils_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - test('get content length lower case', () { - final headers = { - 'content-length': ['12'] - }; - expect(HttpHeaderUtils.getContentLength(headers), 12); - }); - - test('get content length camel case', () { - final headers = { - 'Content-Length': ['12'] - }; - expect(HttpHeaderUtils.getContentLength(headers), 12); - }); -} diff --git a/dart/test/utils/http_sanitizer_test.dart b/dart/test/utils/http_sanitizer_test.dart deleted file mode 100644 index 2a4e0a58be..0000000000 --- a/dart/test/utils/http_sanitizer_test.dart +++ /dev/null @@ -1,189 +0,0 @@ -import 'package:sentry/src/utils/http_sanitizer.dart'; -import 'package:test/test.dart'; - -void main() { - test('returns null for null', () { - expect(HttpSanitizer.sanitizeUrl(null), isNull); - }); - - test('strips user info with user and password from http', () { - final sanitizedUri = HttpSanitizer.sanitizeUrl( - "http://user:password@sentry.io?q=1&s=2&token=secret#top"); - expect(sanitizedUri?.url, "http://[Filtered]:[Filtered]@sentry.io"); - expect(sanitizedUri?.query, "q=1&s=2&token=secret"); - expect(sanitizedUri?.fragment, "top"); - }); - - test('strips user info with user and password from https', () { - final sanitizedUri = HttpSanitizer.sanitizeUrl( - "https://user:password@sentry.io?q=1&s=2&token=secret#top"); - expect(sanitizedUri?.url, "https://[Filtered]:[Filtered]@sentry.io"); - expect(sanitizedUri?.query, "q=1&s=2&token=secret"); - expect(sanitizedUri?.fragment, "top"); - }); - - test('splits url', () { - final sanitizedUri = - HttpSanitizer.sanitizeUrl("https://sentry.io?q=1&s=2&token=secret#top"); - expect(sanitizedUri?.url, "https://sentry.io"); - expect(sanitizedUri?.query, "q=1&s=2&token=secret"); - expect(sanitizedUri?.fragment, "top"); - }); - - test('splits relative url', () { - final sanitizedUri = - HttpSanitizer.sanitizeUrl("/users/1?q=1&s=2&token=secret#top"); - expect(sanitizedUri?.url, "/users/1"); - expect(sanitizedUri?.query, "q=1&s=2&token=secret"); - expect(sanitizedUri?.fragment, "top"); - }); - - test('splits relative root url', () { - final sanitizedUri = - HttpSanitizer.sanitizeUrl("/?q=1&s=2&token=secret#top"); - expect(sanitizedUri?.url, "/"); - expect(sanitizedUri?.query, "q=1&s=2&token=secret"); - expect(sanitizedUri?.fragment, "top"); - }); - - test('splits url with just query and fragment', () { - final sanitizedUri = - HttpSanitizer.sanitizeUrl("/?q=1&s=2&token=secret#top"); - expect(sanitizedUri?.url, "/"); - expect(sanitizedUri?.query, "q=1&s=2&token=secret"); - expect(sanitizedUri?.fragment, "top"); - }); - - test('splits relative url with query only', () { - final sanitizedUri = - HttpSanitizer.sanitizeUrl("/users/1?q=1&s=2&token=secret"); - expect(sanitizedUri?.url, "/users/1"); - expect(sanitizedUri?.query, "q=1&s=2&token=secret"); - expect(sanitizedUri?.fragment, isNull); - }); - - test('splits relative url with fragment only', () { - final sanitizedUri = HttpSanitizer.sanitizeUrl("/users/1#top"); - expect(sanitizedUri?.url, "/users/1"); - expect(sanitizedUri?.query, isNull); - expect(sanitizedUri?.fragment, "top"); - }); - - test('strips user info with user and password without query', () { - final sanitizedUri = - HttpSanitizer.sanitizeUrl("https://user:password@sentry.io#top"); - expect(sanitizedUri?.url, "https://[Filtered]:[Filtered]@sentry.io"); - expect(sanitizedUri?.query, isNull); - expect(sanitizedUri?.fragment, "top"); - }); - - test('splits without query', () { - final sanitizedUri = HttpSanitizer.sanitizeUrl("https://sentry.io#top"); - expect(sanitizedUri?.url, "https://sentry.io"); - expect(sanitizedUri?.query, isNull); - expect(sanitizedUri?.fragment, "top"); - }); - - test('strips user info with user and password without fragment', () { - final sanitizedUri = HttpSanitizer.sanitizeUrl( - "https://user:password@sentry.io?q=1&s=2&token=secret"); - expect(sanitizedUri?.url, "https://[Filtered]:[Filtered]@sentry.io"); - expect(sanitizedUri?.query, "q=1&s=2&token=secret"); - expect(sanitizedUri?.fragment, isNull); - }); - - test('strips user info with user and password without query or fragment', () { - final sanitizedUri = - HttpSanitizer.sanitizeUrl("https://user:password@sentry.io"); - expect(sanitizedUri?.url, "https://[Filtered]:[Filtered]@sentry.io"); - expect(sanitizedUri?.query, isNull); - expect(sanitizedUri?.fragment, isNull); - }); - - test('splits url without query or fragment and no authority', () { - final sanitizedUri = HttpSanitizer.sanitizeUrl("https://sentry.io"); - expect(sanitizedUri?.url, "https://sentry.io"); - expect(sanitizedUri?.query, isNull); - expect(sanitizedUri?.fragment, isNull); - }); - - test('strips user info with user only', () { - final sanitizedUri = HttpSanitizer.sanitizeUrl( - "https://user@sentry.io?q=1&s=2&token=secret#top"); - expect(sanitizedUri?.url, "https://[Filtered]@sentry.io"); - expect(sanitizedUri?.query, "q=1&s=2&token=secret"); - expect(sanitizedUri?.fragment, "top"); - }); - - test('no details extracted with query after fragment', () { - final sanitizedUri = HttpSanitizer.sanitizeUrl( - "https://user:password@sentry.io#fragment?q=1&s=2&token=secret"); - expect(sanitizedUri?.url, isNull); - expect(sanitizedUri?.query, isNull); - expect(sanitizedUri?.fragment, isNull); - }); - - test('no details extracted with query after fragment without authority', () { - final sanitizedUri = HttpSanitizer.sanitizeUrl( - "https://sentry.io#fragment?q=1&s=2&token=secret"); - expect(sanitizedUri?.url, isNull); - expect(sanitizedUri?.query, isNull); - expect(sanitizedUri?.fragment, isNull); - }); - - test('no details extracted from malformed url', () { - final sanitizedUri = HttpSanitizer.sanitizeUrl( - "htps://user@sentry.io#fragment?q=1&s=2&token=secret"); - expect(sanitizedUri?.url, isNull); - expect(sanitizedUri?.query, isNull); - expect(sanitizedUri?.fragment, isNull); - }); - - test('removes security headers', () { - final securityHeaders = [ - "X-FORWARDED-FOR", - "AUTHORIZATION", - "COOKIE", - "SET-COOKIE", - "X-API-KEY", - "X-REAL-IP", - "REMOTE-ADDR", - "FORWARDED", - "PROXY-AUTHORIZATION", - "X-CSRF-TOKEN", - "X-CSRFTOKEN", - "X-XSRF-TOKEN" - ]; - - final headers = {}; - for (final securityHeader in securityHeaders) { - headers[securityHeader] = 'foo'; - headers[securityHeader.toLowerCase()] = 'bar'; - headers[securityHeader._capitalize()] = 'baz'; - } - final sanitizedHeaders = HttpSanitizer.sanitizedHeaders(headers); - expect(sanitizedHeaders, isNotNull); - expect(sanitizedHeaders?.isEmpty, true); - }); - - test('handle throwing uri', () { - final details = HttpSanitizer.sanitizeUrl('::Not valid URI::'); - expect(details, isNull); - }); - - test('keeps email address', () { - final urlDetails = HttpSanitizer.sanitizeUrl( - "https://staging.server.com/api/v4/auth/password/reset/email@example.com"); - expect( - "https://staging.server.com/api/v4/auth/password/reset/email@example.com", - urlDetails?.url); - expect(urlDetails?.query, isNull); - expect(urlDetails?.fragment, isNull); - }); -} - -extension _StringExtension on String { - String _capitalize() { - return "${this[0].toUpperCase()}${substring(1).toLowerCase()}"; - } -} diff --git a/dart/test/utils/regex_utils_test.dart b/dart/test/utils/regex_utils_test.dart deleted file mode 100644 index ff098ab964..0000000000 --- a/dart/test/utils/regex_utils_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:sentry/src/utils/regex_utils.dart'; -import 'package:test/test.dart'; - -void main() { - group('regex_utils', () { - final testString = "this is a test"; - - test('testString contains string pattern', () { - expect(isMatchingRegexPattern(testString, ["is"]), isTrue); - }); - - test('testString does not contain string pattern', () { - expect(isMatchingRegexPattern(testString, ["not"]), isFalse); - }); - - test('testString contains regex pattern', () { - expect(isMatchingRegexPattern(testString, ["^this.*\$"]), isTrue); - }); - - test('testString does not contain regex pattern', () { - expect(isMatchingRegexPattern(testString, ["^is.*\$"]), isFalse); - }); - }); -} diff --git a/dart/test/utils/sample_rate_format_test.dart b/dart/test/utils/sample_rate_format_test.dart deleted file mode 100644 index a9b4c80b09..0000000000 --- a/dart/test/utils/sample_rate_format_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:sentry/src/utils/sample_rate_format.dart'; -import 'package:test/test.dart'; -import 'package:intl/intl.dart'; - -void main() { - test('format', () { - final inputs = [ - 0.0, - 1.0, - 0.1, - 0.11, - 0.19, - 0.191, - 0.1919, - 0.19191, - 0.191919, - 0.1919191, - 0.19191919, - 0.191919191, - 0.1919191919, - 0.19191919191, - 0.191919191919, - 0.1919191919191, - 0.19191919191919, - 0.191919191919191, - 0.1919191919191919, - 0.19191919191919199, - ]; - - for (final input in inputs) { - expect( - SampleRateFormat().format(input), - NumberFormat('#.################').format(input), - ); - } - }); - - test('input smaller 0 is capped', () { - expect(SampleRateFormat().format(-1), '0'); - }); - - test('input larger 1 is capped', () { - expect(SampleRateFormat().format(1.1), '1'); - }); - - test('call with NaN returns 0', () { - expect(SampleRateFormat().format(double.nan), '0'); - }); -} diff --git a/dart/test/utils/tracing_utils_test.dart b/dart/test/utils/tracing_utils_test.dart deleted file mode 100644 index b5e7b70b52..0000000000 --- a/dart/test/utils/tracing_utils_test.dart +++ /dev/null @@ -1,357 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/sentry_tracer.dart'; -import 'package:test/test.dart'; - -import '../mocks/mock_sentry_client.dart'; -import '../test_utils.dart'; - -void main() { - group('$containsTargetOrMatchesRegExp', () { - final origins = ['localhost', '^(http|https)://api\\..*\$']; - - test('origins contains the url when it contains one of the defined origins', - () { - expect( - containsTargetOrMatchesRegExp(origins, 'http://localhost:8080/foo'), - isTrue); - expect( - containsTargetOrMatchesRegExp( - origins, 'http://xxx.localhost:8080/foo'), - isTrue); - }); - - test('origins contain the url when it matches regex', () { - expect( - containsTargetOrMatchesRegExp(origins, 'http://api.foo.bar:8080/foo'), - isTrue); - expect( - containsTargetOrMatchesRegExp( - origins, 'https://api.foo.bar:8080/foo'), - isTrue); - expect( - containsTargetOrMatchesRegExp( - origins, 'http://api.localhost:8080/foo'), - isTrue); - expect( - containsTargetOrMatchesRegExp(origins, 'ftp://api.foo.bar:8080/foo'), - isFalse); - }); - - test('invalid regex do not throw', () { - expect( - containsTargetOrMatchesRegExp( - ['AABB???', '^(http|https)://api\\..*\$'], - 'http://api.foo.bar:8080/foo'), - isTrue); - }); - - test('when no origins are defined, returns false for every url', () { - expect(containsTargetOrMatchesRegExp([], 'api.foo.com'), isFalse); - }); - }); - - group('$addSentryTraceHeaderFromSpan', () { - final fixture = Fixture(); - - test('adds sentry trace header from span', () { - final headers = {}; - final sut = fixture.getSut(); - final sentryHeader = sut.toSentryTrace(); - - addSentryTraceHeaderFromSpan(sut, headers); - - expect(headers[sentryHeader.name], sentryHeader.value); - }); - - test('adds sentry trace header', () { - final headers = {}; - final sut = fixture.getSut(); - final sentryHeader = sut.toSentryTrace(); - - addSentryTraceHeader(sentryHeader, headers); - - expect(headers[sentryHeader.name], sentryHeader.value); - }); - }); - - group('$addBaggageHeader', () { - final fixture = Fixture(); - - test('adds baggage header', () { - final headers = {}; - final sut = fixture.getSut(); - final baggage = sut.toBaggageHeader(); - - addBaggageHeader(sut.toBaggageHeader()!, headers); - - expect(headers[baggage!.name], baggage.value); - }); - - test('adds baggage header from span', () { - final headers = {}; - final sut = fixture.getSut(); - final baggage = sut.toBaggageHeader(); - - addBaggageHeaderFromSpan(sut, headers); - - expect(headers[baggage!.name], baggage.value); - }); - - test('appends baggage header from span', () { - final headers = {}; - final oldValue = 'other-vendor-value-1=foo'; - headers['baggage'] = oldValue; - - final sut = fixture.getSut(); - final baggage = sut.toBaggageHeader(); - - final newValue = '$oldValue,${baggage!.value}'; - - addBaggageHeaderFromSpan(sut, headers); - - expect(headers[baggage.name], newValue); - }); - - test('overwrites duplicate key values', () { - final headers = {}; - final oldValue = - 'other-vendor-value=foo,sentry-trace_id=${SentryId.newId()},sentry-public_key=oldPublicKey,sentry-release=oldRelease,sentry-environment=oldEnvironment,sentry-user_id=oldUserId,sentry-transaction=oldTransaction,sentry-sample_rate=0.5'; - - headers['baggage'] = oldValue; - - final sut = fixture.getSut(); - final baggage = sut.toBaggageHeader(); - - addBaggageHeaderFromSpan(sut, headers); - - expect(headers[baggage!.name], - 'other-vendor-value=foo,sentry-trace_id=${sut.context.traceId},sentry-public_key=public,sentry-release=release,sentry-environment=environment,sentry-transaction=name,sentry-sample_rate=1,sentry-sampled=true'); - }); - }); - - group('$isValidSampleRate', () { - test('returns false if null sampleRate', () { - expect(isValidSampleRate(null), false); - }); - - test('returns true if 1', () { - expect(isValidSampleRate(1.0), true); - }); - - test('returns true if 0', () { - expect(isValidSampleRate(0.0), true); - }); - - test('returns false if below the range', () { - expect(isValidSampleRate(-0.01), false); - }); - - test('returns false if above the range', () { - expect(isValidSampleRate(1.01), false); - }); - - test('returns false if NaN', () { - expect(isValidSampleRate(double.nan), false); - }); - }); - - group('$generateSentryTraceHeader', () { - test('generates header with new ids when not provided', () { - final header = generateSentryTraceHeader(); - - expect(header.traceId, isNotNull); - expect(header.spanId, isNotNull); - expect(header.sampled, isNull); - }); - - test('generates header with provided traceId', () { - final traceId = SentryId.newId(); - final header = generateSentryTraceHeader(traceId: traceId); - - expect(header.traceId, traceId); - expect(header.spanId, isNotNull); - expect(header.sampled, isNull); - }); - - test('generates header with provided spanId', () { - final spanId = SpanId.newId(); - final header = generateSentryTraceHeader(spanId: spanId); - - expect(header.traceId, isNotNull); - expect(header.spanId, spanId); - expect(header.sampled, isNull); - }); - - test('generates header with provided sampled decision', () { - final header = generateSentryTraceHeader(sampled: true); - - expect(header.traceId, isNotNull); - expect(header.spanId, isNotNull); - expect(header.sampled, true); - }); - - test('generates header with all parameters provided', () { - final traceId = SentryId.newId(); - final spanId = SpanId.newId(); - final header = generateSentryTraceHeader( - traceId: traceId, - spanId: spanId, - sampled: false, - ); - - expect(header.traceId, traceId); - expect(header.spanId, spanId); - expect(header.sampled, false); - }); - }); - - group('$addTracingHeadersToHttpHeader', () { - final fixture = Fixture(); - - test('adds headers from span when span is provided', () { - final headers = {}; - final hub = fixture._hub; - final span = fixture.getSut(); - - addTracingHeadersToHttpHeader(headers, hub, span: span); - - final traceHeader = - SentryTraceHeader.fromTraceHeader(headers['sentry-trace']); - expect(traceHeader.traceId, span.context.traceId); - expect(traceHeader.spanId, span.context.spanId); - expect(traceHeader.sampled, span.samplingDecision?.sampled); - expect(headers['baggage'], isNotNull); - }); - - test('adds headers from scope when span is null', () { - final headers = {}; - final hub = fixture._hub; - hub.configureScope((scope) { - scope.propagationContext.baggage = SentryBaggage({'test': 'value'}); - }); - - addTracingHeadersToHttpHeader(headers, hub); - - final traceHeader = - SentryTraceHeader.fromTraceHeader(headers['sentry-trace']); - expect(traceHeader.traceId, hub.scope.propagationContext.traceId); - expect(headers['baggage'], contains('test=value')); - }); - }); - - group('$addSentryTraceHeaderFromScope', () { - test('adds sentry trace header from scope propagation context', () { - final fixture = Fixture(); - final headers = {}; - final hub = fixture._hub; - final scope = hub.scope; - - addSentryTraceHeaderFromScope(scope, headers); - - final traceHeader = - SentryTraceHeader.fromTraceHeader(headers['sentry-trace']); - expect(traceHeader.traceId, scope.propagationContext.traceId); - }); - }); - - group('$addBaggageHeaderFromScope', () { - test('adds baggage header from scope when baggage exists', () { - final fixture = Fixture(); - final headers = {}; - final hub = fixture._hub; - final scope = hub.scope; - scope.propagationContext.baggage = SentryBaggage({ - 'sentry-trace_id': scope.propagationContext.traceId.toString(), - 'sentry-public_key': 'public', - 'custom': 'value', - }); - - addBaggageHeaderFromScope(scope, headers); - - expect(headers['baggage'], isNotNull); - expect(headers['baggage'], contains('custom=value')); - expect(headers['baggage'], contains('sentry-public_key=public')); - }); - - test('does not add baggage header when baggage is null', () { - final fixture = Fixture(); - final headers = {}; - final hub = fixture._hub; - final scope = hub.scope; - scope.propagationContext.baggage = null; - - addBaggageHeaderFromScope(scope, headers); - - expect(headers['baggage'], isNull); - }); - }); - - group('$isValidSampleRand', () { - test('returns false if null sampleRand', () { - expect(isValidSampleRand(null), false); - }); - - test('returns true if 0', () { - expect(isValidSampleRand(0.0), true); - }); - - test('returns true if 0.5', () { - expect(isValidSampleRand(0.5), true); - }); - - test('returns true if 0.999', () { - expect(isValidSampleRand(0.999), true); - }); - - test('returns false if 1.0', () { - expect(isValidSampleRand(1.0), false); - }); - - test('returns false if below the range', () { - expect(isValidSampleRand(-0.01), false); - }); - - test('returns false if above the range', () { - expect(isValidSampleRand(1.01), false); - }); - - test('returns false if NaN', () { - expect(isValidSampleRand(double.nan), false); - }); - }); -} - -class Fixture { - Fixture() { - _hub = Hub(_options); - _hub.configureScope((scope) => scope.setUser(_user)); - - _hub.bindClient(_client); - } - - final _context = SentryTransactionContext( - 'name', - 'op', - transactionNameSource: SentryTransactionNameSource.custom, - samplingDecision: SentryTracesSamplingDecision( - true, - sampleRate: 1.0, - ), - ); - - final _options = defaultTestOptions() - ..release = 'release' - ..environment = 'environment'; - - late Hub _hub; - - final _client = MockSentryClient(); - - final _user = SentryUser( - id: 'id', - ); - - SentryTracer getSut() { - return SentryTracer(_context, _hub); - } -} diff --git a/dart/test/utils/url_details_test.dart b/dart/test/utils/url_details_test.dart deleted file mode 100644 index 673d4452da..0000000000 --- a/dart/test/utils/url_details_test.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:mockito/mockito.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - test('does not crash on null span', () { - final urlDetails = - UrlDetails(url: "https://sentry.io/api", query: "q=1", fragment: "top"); - urlDetails.applyToSpan(null); - }); - - test('applies all to span', () { - final urlDetails = - UrlDetails(url: "https://sentry.io/api", query: "q=1", fragment: "top"); - final span = MockSpan(); - urlDetails.applyToSpan(span); - - verify(span.setData("url", "https://sentry.io/api")); - verify(span.setData("http.query", "q=1")); - verify(span.setData("http.fragment", "top")); - }); - - test('applies only url to span', () { - final urlDetails = UrlDetails(url: "https://sentry.io/api"); - final span = MockSpan(); - urlDetails.applyToSpan(span); - - verify(span.setData("url", "https://sentry.io/api")); - verifyNoMoreInteractions(span); - }); - - test('applies only query to span', () { - final urlDetails = UrlDetails(query: "q=1"); - final span = MockSpan(); - urlDetails.applyToSpan(span); - - verify(span.setData("http.query", "q=1")); - verifyNoMoreInteractions(span); - }); - - test('applies only fragment to span', () { - final urlDetails = UrlDetails(fragment: "top"); - final span = MockSpan(); - urlDetails.applyToSpan(span); - - verify(span.setData("http.fragment", "top")); - verifyNoMoreInteractions(span); - }); - - test('applies details to request', () { - final url = "https://sentry.io/api?q=1#top"; - final request = SentryRequest(url: url); - request.sanitize(); - - expect(request.url, "https://sentry.io/api"); - expect(request.queryString, "q=1"); - expect(request.fragment, "top"); - }); - - test('applies details without fragment and url to request', () { - final request = SentryRequest(url: 'https://sentry.io/api'); - request.sanitize(); - - expect(request.url, "https://sentry.io/api"); - expect(request.queryString, isNull); - expect(request.fragment, isNull); - }); - - test('removes cookies from request', () { - final request = - SentryRequest(url: 'https://sentry.io/api', cookies: 'foo=bar'); - request.sanitize(); - expect(request.cookies, isNull); - }); - - test('returns fallback for null URL', () { - final urlDetails = UrlDetails(url: null); - expect(urlDetails.urlOrFallback, "unknown"); - }); - - test('returns fallback for invalid Uri', () { - final urlDetails = UrlDetails(url: 'htttps://[Filtered].com/foobar.txt'); - - expect(urlDetails.urlOrFallback, "unknown"); - expect(Uri.parse(urlDetails.urlOrFallback), isNotNull); - }); -} - -class MockSpan extends Mock implements SentrySpan { - final SentrySpanContext _context = SentrySpanContext(operation: 'test'); - @override - SentrySpanContext get context => _context; -} diff --git a/dart/test/utils_test.dart b/dart/test/utils_test.dart deleted file mode 100644 index a2012a6fe3..0000000000 --- a/dart/test/utils_test.dart +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:sentry/src/protocol/sentry_device.dart'; -import 'package:test/test.dart'; - -import 'package:sentry/src/utils.dart'; - -void main() { - group('formatDateAsIso8601WithSecondPrecision', () { - test('strips sub-millisecond parts', () { - final testDate = - DateTime.fromMillisecondsSinceEpoch(1502467721598, isUtc: true); - expect(testDate.toIso8601String(), '2017-08-11T16:08:41.598Z'); - expect(formatDateAsIso8601WithMillisPrecision(testDate), - '2017-08-11T16:08:41.598Z'); - }); - - test('non enum returns toString serialization', () { - final value = true; - expect(jsonSerializationFallback(value), 'true'); - }); - - test('enum returns described enum during serialization', () { - expect(jsonSerializationFallback(SentryOrientation.landscape), - 'SentryOrientation.landscape'); - }); - - test('null Object returns null during serialization', () { - expect(jsonSerializationFallback(null), null); - }); - }); -} diff --git a/dart/test/version_test.dart b/dart/test/version_test.dart deleted file mode 100644 index ada7310338..0000000000 --- a/dart/test/version_test.dart +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@TestOn('vm') -library; - -import 'dart:io'; - -import 'package:sentry/src/version.dart'; -import 'package:test/test.dart'; -import 'package:yaml/yaml.dart' as yaml; - -void main() { - group('sdkVersion', () { - test('matches that of pubspec.yaml', () { - final dynamic pubspec = - yaml.loadYaml(File('pubspec.yaml').readAsStringSync()); - expect(sdkVersion, pubspec['version']); - }); - }); -} diff --git a/dart/test_resources/envelope-no-attachment.envelope b/dart/test_resources/envelope-no-attachment.envelope deleted file mode 100644 index 0faac03efa..0000000000 --- a/dart/test_resources/envelope-no-attachment.envelope +++ /dev/null @@ -1,3 +0,0 @@ -{"event_id":"00000000000000000000000000000000","sdk":{"name":"","version":""},"dsn":"https://abc@def.ingest.sentry.io/1234567"} -{"content_type":"application/json","type":"event","length":86} -{"event_id":"00000000000000000000000000000000","timestamp":"1970-01-01T00:00:00.000Z"} \ No newline at end of file diff --git a/dart/test_resources/envelope-with-image.envelope b/dart/test_resources/envelope-with-image.envelope deleted file mode 100644 index 533f5e4f6fc9c60af5fbb0311fdb5fdfc2e074e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3712 zcmV-`4uA1`B4u`EZggL1WFk5uGh#D1GG;O|Wo0%uG&wP5I51{6WMySBI506bVK-ti zA}k_vWNRWidm?UOZDk@lB6MYQbRsMwc4cyNX>V>KIwE#ua&u{KZX$hs3VR}BZ*FvD zZggLCd2nSSIwEOpVP|D8aBgQJEFxxUY-Mg?ZDk@lB6DSKbaHtvaBgQJEFyGyaAhJo zB4KoNVPj}*Wo~pLEFx@WZfA68B04iQGc|n*iBL{Q4GJ0x0000DNk~Le0002t0002t z2m=5B010g(NB{r;hfqvZMgRZ*0002#>FMU?=5B6oL_|cdudn?4{Q3F$?Ck9I_4Ufi z%J1**_xJaHetwaWkx5BOzrVlV-`~y6&DPe|TwGi@I5<8&K4D>DxVX4xW@gjV(C;AvQ<@8V#tQV z000dGNklX+z>l6NQb-un3BCU1ku`I9_y?|Nr~m0TF?Es=7K!C*i(1A0=R~ zb*fHPcaDxabm-8bLx&C>I&}EILw__F44ZC5G7PUS^7+fd!|d+n^1R=4YpTcjO}2yS zKTWr&T3p>`0=MT)x2ifE-^l(gaXMAeb@0E? zMP&{5x|zlCkE?@+ zY){XeJ-lnWp+)iC*}*LP(sWY`!rj3v>owihLb$}f%wOrR7fm;|2#_g$433U|(Fcd< z+5Dsqu0Ht}KKa&gYYX6-W@`NzeROz?F@auuUw?K5pUoR>Z2?dxCYw)l`1DtUtu2BD z^x=NKsu+UNpdo+pn#uu6fO^L1cet~aH%x^S` z6KHNovA=`wd=Gr5IsAdvhRS9RiZQ6M;mp|-zSY*GDO3ezC&6#g(sq2U36RmSnBoIu zo=%guwg85tOSryCli0?pH<}8cQKXuw7A8$8)#N2M$`$`k+S(!@C)(?rV7|X?5;!r1 z-@&As4Udtk6SUC$wUq}Tfs?U_3G^r<-l{U2J}^a`y!!%OY*W&drXWsmp~{q5qSh7w z35c;JhdKO4dy=N)JZF=*g1me9iA7r^aIJhTZVIS&qb*5ODoR-4CPq{m(l4}wF^uxH zMU1(j%y=u3rag)&Hi+;{dZa|QfibvzZG$_1FiUyI79>qk58Q!&rIOCmf~4sHxndh` z)dlnaZ8$k}PXf(0CSG2n8jh!gE%)Quv^IZtlQZ#&jM0vy>Bv0Xl(|J-%8=)2o9@On ziPu~3ZN}alzS^3k>2HdFKH(Dxi7{RhH{6dg$!(wbST3xs|m+IW(rnWw5 zY9HnD_&2RDvpG>CjU^oV;sKP|nA7O+q$yK=mcH3B_=<-{)^M19a*^7WG^M@dZ!4+G zFKv^+iPk<4&PWhrw64tN*f^ONoJBlP)V`CZhNl|bi!Df+4jmax$_YEWI3@?M-ZGn~ z>}S}|xYixj&;Zfs)S5J1xmIC)VW2y4A(@cAS!dETU;W3|Fn#M+Mg*1ssmw3qikctB zMj<^7E>$($cnw4vG&Z%B+4N_h|MW3*f>-t70`<?ovfp{1%y;(>sHbl93s zn(mM8G@$ok<=PUqo6@Ia8}*dg$Q=?mPOo0AVACRH&TF{{g{I`jo<=+#vxLnV!i=kK^xi5$~5R#b#XQ>$hSr~-LUcLhW5aU3tig*0Q6}YgT2_l$O;e=?+zU zquTW7dSEKdtgy_6hJ5E-nwp+a^-ZD-OAJlS7$mRlHb+fQXqj6&8*3I^Bu$!UsTCp&d*;I5lfpdLm#Hbl{65q@dB*_@VT}r1# zR~nVm$XSd*LsCi7l0Fqp6jq!xWxKJ&)KK$KbI`04ID2OBj)tLdVJ4nJTI1H>A&hU8(vlvqk?BuY${6+= zMhuI9%&&L}U>?l|_lPlyO5lOl;22(S#6@#-(#cHK)9@88%pBRXXsEcPYbfo8Nwg2C zQ`l8JX?m}!OiSo)FO_U)5jiY7)BfW&FTohPJv9fL9CpQmz$DsPiK;JjAE17_hE7Ew zhHEq+*~KnKz{1Q@61ZAim>CO|3Plu?2Nun)L9G66f}SyrY9s{?*8aWhYXI*CZr`br zG1f5hziNFsP=;difW_^=7GD8UGHH6Om))z>FWnWzcLP@}kO9r19i_``)Rnr>+(OvD zO2efB6DXCfnlav?@`k!TewpD=faLAS@uBE^nC?RL)i|L?zNP&c#vONBG+7)z-cut zY{vLVhfV_AiIIlF(K0q-pHnW=U=QPyrdwljW*Tdho|P>iy;HW5a_xr`XVJIW?C|r2 zHQjVLP&21OljLiW#*axm$R0#4>dVy;KaKyR`D&c*8D5 z)O^ZXbdKcYhIN#6MI}u^E-}Oe`f0^m7C$`0a9sMPo@2iex7nZ!X^dhHkMky%_5hdZ zSj@GjmISl7{3zYi2!%FU?8V@=D3>x<;QS|0~Cv}b1Lh# zkT2v$^z{%Aitnx_lI%TX1e=_VgD~m*>*Cj(rH4Vrz$=P9#~Hv_+RSxv3DVu;=>K;ww6XAmwYkGpsbbz8BkqY6hMBnOuy`n`kFDMGJCtRUR3 z!sJF-1!+|lboy?7brOStl-zvo3KF7-5wM3tY{_>}_}`9l`N#4=LiI%#f>e-t%B z9#o8#7f_Z2?l9+dZ~MR_VeTF-oe%4*K~eaeY9DaLyL-6uD$Ezd6a$L!1$&6$ledTO zK}3Q{#OS1n9XhjeAzG%PpE*5R*q2N(0#Vd;DdpLve=GZ>g9EHYv_^cd~bKQjO zLGm7+Pfr1NOTl7sc4^h6-ak(#^B2dZ);5y!VL9|igX5Awov>Hzx6;4vp+kob9XfRA e(4j+z^!OjB9jyEhAyhE{0000E`C4~jn{Grh46iQo`OCw@?C$3Byx(+ds>k_Fwu9+EO}D68T-{~@x93f_syZCs$o?&H zI%>LQ72!Ac?=K#%n{Hbrcv3s~7yp5k+WTg8@W0SSWea}dYtt<(iPKOAv)iVdSPsv@ z4rbY9(~T^HV@C(G?6~P>mOyxoE-`JonZ@ystAmGZPtTk^ylc9lMe*I)!7TgIbW;n$ z-N7vDHQm-ixWvB9U+J$GO*gg(kSTr)j*foO2Z!j{{G<-9KKU0u`POi23*ee&YW*2~ zba;$0fnI!He|80*%^PiP0Z=C*m__@4;&I>&=fuHJVAluO+&3M0NQ4Yy=NZanX5)xTLihI_2=-+TO+M40Ah+i z;wh4-o*QUw5xha$rSyx8aTs|iG9pj#+yy-M6P{}wkSSP}d7n%l5iLLg2fp1LAZ#0S%Xl_Wczk~054}7ON{DIbn%4QCV zF{rWO%-Iya)z+jbR0U-x!Ee#hc6_Y~kkPQ1;sa!!PLsE`0EVPXxV}k~*v6_inhKv$ zq?)M~CQT{Th_NMyIs8U@lBVQ5XOp;synFbG zMO!3rt$ZzR3aECYElE=;VjFJN1@r%HI5~7r0?jriUS6Xbj;Dkz z_v6{LHh*`MGx3Rx(T=3)$UNMXxkX;ekmqTe?#4BV*IV#y#@-yh+M1;4Z;F6E;S&gn zFbHIZJ+q#vNu#_^BDeGUAQ-w>fGa|wmxZUALa7+H?1$TIZ-2xB^>(V0hHO8 z)9CP|DN}xyzS%MOiibwlaF~8_k=m9trM={DE2+ybZIi%>);JG12@jhtr=0M-z0wePSU#xjF4{)i{ca`WwIh^lL^ag#(^Z-5>`y5 zxZzgFKs=MM%*K+?Hn3`7IG8d^^lD9-YP`LNZ#s?lc^_Nu0y0LbGMn7j!)&6P$74De zWjiW0CQbbpTg~+rGu=EH{}#(^k|a&Hhuy8N2r>MXkZNbjN^Y@m*6 zMnyS%Awii9sR~nFE}W@d?4bZ>aU5E>>dR~(m#`R)^Wo9Md*dRD?%Ut7Z(S0(nn(>5iP9)udusGb*ZqG3Z~c1qJA;zdQ zX-ZpXQx|zc?Oj1#dB}>^vZK&zR%OzZmenWe4pn=j+VtppU@FY4u*`;reCJ%6nx0Vg zO`;4-3{A`!B(Lo@M@>&?nOiy=YZhE2O`4Lq+}GDNkLOm~EQaku3=<|z@wbIp&{Rg1 znQ5I`u`rd{RCG6ibA4#Us2Oz<-^>#v$r!_3N~c9v8kN(?S&TtLQc2R3>@*Xd(nT%* z-iK#R+E0`;MJG}*(@Gb$R?xb(YzN*=P`)sevUn=hDtl3DHmEkHqMamZN-XfEyGwWr zvU*gKc9x}2V3|!x2^<%OMO);K>FX4{K~vGDHBz!L6B99}$IHcDian(PH0Okz{8C9% zj*RU0-{4D0v|tW)mRb6QCr!&Rh9<9BI#3T)Uxk|KT$rg8R-80tyRpR7Q1ei8(5w?U zduH&ChM{m_CZ0lCH%@)-dwFYJE9ShGO!7#qGcrUjb4w zX?m=e-K*3u-4(@m16M4N0nMQurORy8mAcT}LfF4b!=(ZfD3z_6G2Wr_hPpoFpeR)r zuxA6dF-_7`wW%7HL&{2-MCn)rLL#gJEq~JV8mnP4-9?pVT`{MU^&bLlA*Z>5!pwmd z5^Gyg4hd@xpq+-w@)NxRWUO>i8$HP~%7rs>UxoZIOqy0uW;0UG_-b;HOw92(OXD~X zrFc`_O)w&fI8)!fA-P%cP9@)l>;@)Oswm7Hu({Ffsf-l4<6$~8FD#^ zPxzelYl7!vP)7Q?`OR1BcIwDR6~!!AYCe9BsMj^yNqb(D2QB~3vtF~kJ= zX~kO>KRm;5T>7S-W4{r%*`N$*jA9Ot^Cp+}0GH`l%(bVM1hcsODBbXA2|{E~6_OSq zJWsZ0flCB+Crv4VY{y%ml_+AhaI3jGLl|hi9DWP#SAbBu$mV@AHoS9WBhV(v)$BSF z>%7pNG~ZAm2WTZq^dfc`FMnaoeRUSlbh*^)=zo1Be< zFzNj3;@6y|he5``D~dhG8NgCI<6Dj<^Zm!13TW`8DOJh(%$N+&*!>n`_=4V&SQFk; zc!4q-O2;|xNr5OEn=a31qM;U?<<7Q{1pc|#N$zmobpt0$d0`3t79BjX>fIO85T9@4 z5)zi&g;ZWSUU&yMJJP0m+i{dbFV>@2?zIcjP;+`oEay99MBzKSPyQHh!6qRfqM%Va zr^pv_cChWj?E8qm;zwngc&Q2aj8`4E}%mzyc!h!1YN{b z#}co9C8~%#LoD41^s0rPf^ojMhc4=P8u(_v$Y+f#X^{@T8D7Ig<5Fvcm= zdBm2bNMnqFrQEL=H{^Q{xulv6q+y?fKNp+r?~6g=l_i8tbPV@dO|7_Mh~6wfhm4^3B7Ww7|`nz>+fN5CLyK)cWe0K zxDQ;KH{cfruD^o+2^~{^53kI_O@;A9t48XS@RE(7uP?iMlFz0n|`@kb%?jA0k z59_Q!QTUu{A8^IHd${r{%ooEH1B&qldx+taw}2-mm2)C0h4uGv%eAW7v1vyScdT&e^@WjWMGyDBXutL@ z7H#OE;885aD~jfOID_!!*gn=Ag~ogMtQ?Ya-GuBx@*bW~PXTvJ!D4ZCY1O6PKTjv~ z7ssX6Hj?vUIrK+^I&|pJp+krC_#dbpto#olR51Vm002ov JPDHLkV1hwO&o}@8 diff --git a/dart/test_resources/testfile.txt b/dart/test_resources/testfile.txt deleted file mode 100644 index 96c906756d..0000000000 --- a/dart/test_resources/testfile.txt +++ /dev/null @@ -1 +0,0 @@ -foo bar \ No newline at end of file diff --git a/dart/tool/presubmit.sh b/dart/tool/presubmit.sh deleted file mode 100755 index b64e684920..0000000000 --- a/dart/tool/presubmit.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -set -e -set -x - -# https://dart.dev/tools/dart-tool - -# get current package's dependencies -dart pub get -# static code analyzer -dart analyze --fatal-infos -# tests -dart test -p "chrome,vm" -# formatting -dart format --set-exit-if-changed ./ -# pub score -pana -# dry publish -dart pub publish --dry-run diff --git a/melos.yaml b/melos.yaml new file mode 100644 index 0000000000..b75129a056 --- /dev/null +++ b/melos.yaml @@ -0,0 +1,7 @@ +name: sentry_dart_workspace +packages: + - 'packages/*' + +ide: + intellij: + moduleNamePrefix: '' diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000000..8745560d7f --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,9 @@ +name: sentry_dart_workspace +publish_to: 'none' +repository: https://github.com/getsentry/sentry-dart/ + +environment: + sdk: '>=3.5.0 <4.0.0' + +dev_dependencies: + melos: ^6.3.3 \ No newline at end of file From 82fa1bc52f8d0ea8ed541b7e80a4878a5e463f20 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 30 Jul 2025 12:51:02 +0200 Subject: [PATCH 02/45] Update --- melos.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/melos.yaml b/melos.yaml index b75129a056..f19567daf2 100644 --- a/melos.yaml +++ b/melos.yaml @@ -1,6 +1,6 @@ name: sentry_dart_workspace packages: - - 'packages/*' + - 'packages/**' ide: intellij: From af3e3eea8efc61ba6c276ca2e039b62c660a8fa4 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 30 Jul 2025 12:55:37 +0200 Subject: [PATCH 03/45] Update] --- .github/workflows/dart.yml | 2 +- dio/pubspec_overrides.yaml | 2 +- drift/pubspec_overrides.yaml | 2 +- file/pubspec_overrides.yaml | 2 +- firebase_remote_config/pubspec_overrides.yaml | 2 +- flutter/pubspec_overrides.yaml | 2 +- hive/pubspec_overrides.yaml | 2 +- isar/pubspec_overrides.yaml | 2 +- link/pubspec_overrides.yaml | 2 +- logging/pubspec_overrides.yaml | 2 +- min_version_test/pubspec.yaml | 2 +- sqflite/pubspec_overrides.yaml | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index e9ffa6dc21..35f5ce4e15 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -12,7 +12,7 @@ on: - '.github/workflows/analyze.yml' - '.github/actions/dart-test/**' - '.github/actions/coverage/**' - - 'dart/**' + - 'packages/dart/**' jobs: cancel-previous-workflow: diff --git a/dio/pubspec_overrides.yaml b/dio/pubspec_overrides.yaml index 16e71d16f0..0a17afda20 100644 --- a/dio/pubspec_overrides.yaml +++ b/dio/pubspec_overrides.yaml @@ -1,3 +1,3 @@ dependency_overrides: sentry: - path: ../dart + path: ../packages/dart diff --git a/drift/pubspec_overrides.yaml b/drift/pubspec_overrides.yaml index 16e71d16f0..0a17afda20 100644 --- a/drift/pubspec_overrides.yaml +++ b/drift/pubspec_overrides.yaml @@ -1,3 +1,3 @@ dependency_overrides: sentry: - path: ../dart + path: ../packages/dart diff --git a/file/pubspec_overrides.yaml b/file/pubspec_overrides.yaml index 16e71d16f0..0a17afda20 100644 --- a/file/pubspec_overrides.yaml +++ b/file/pubspec_overrides.yaml @@ -1,3 +1,3 @@ dependency_overrides: sentry: - path: ../dart + path: ../packages/dart diff --git a/firebase_remote_config/pubspec_overrides.yaml b/firebase_remote_config/pubspec_overrides.yaml index 16e71d16f0..0a17afda20 100644 --- a/firebase_remote_config/pubspec_overrides.yaml +++ b/firebase_remote_config/pubspec_overrides.yaml @@ -1,3 +1,3 @@ dependency_overrides: sentry: - path: ../dart + path: ../packages/dart diff --git a/flutter/pubspec_overrides.yaml b/flutter/pubspec_overrides.yaml index 16e71d16f0..0a17afda20 100644 --- a/flutter/pubspec_overrides.yaml +++ b/flutter/pubspec_overrides.yaml @@ -1,3 +1,3 @@ dependency_overrides: sentry: - path: ../dart + path: ../packages/dart diff --git a/hive/pubspec_overrides.yaml b/hive/pubspec_overrides.yaml index 16e71d16f0..0a17afda20 100644 --- a/hive/pubspec_overrides.yaml +++ b/hive/pubspec_overrides.yaml @@ -1,3 +1,3 @@ dependency_overrides: sentry: - path: ../dart + path: ../packages/dart diff --git a/isar/pubspec_overrides.yaml b/isar/pubspec_overrides.yaml index 16e71d16f0..0a17afda20 100644 --- a/isar/pubspec_overrides.yaml +++ b/isar/pubspec_overrides.yaml @@ -1,3 +1,3 @@ dependency_overrides: sentry: - path: ../dart + path: ../packages/dart diff --git a/link/pubspec_overrides.yaml b/link/pubspec_overrides.yaml index 16e71d16f0..0a17afda20 100644 --- a/link/pubspec_overrides.yaml +++ b/link/pubspec_overrides.yaml @@ -1,3 +1,3 @@ dependency_overrides: sentry: - path: ../dart + path: ../packages/dart diff --git a/logging/pubspec_overrides.yaml b/logging/pubspec_overrides.yaml index 16e71d16f0..0a17afda20 100644 --- a/logging/pubspec_overrides.yaml +++ b/logging/pubspec_overrides.yaml @@ -1,3 +1,3 @@ dependency_overrides: sentry: - path: ../dart + path: ../packages/dart diff --git a/min_version_test/pubspec.yaml b/min_version_test/pubspec.yaml index 40bcc4a009..791668d1b9 100644 --- a/min_version_test/pubspec.yaml +++ b/min_version_test/pubspec.yaml @@ -44,7 +44,7 @@ dependency_overrides: flutter: sdk: flutter sentry: - path: ../dart + path: ../packages/dart sentry_flutter: path: ../flutter sentry_dio: diff --git a/sqflite/pubspec_overrides.yaml b/sqflite/pubspec_overrides.yaml index 16e71d16f0..0a17afda20 100644 --- a/sqflite/pubspec_overrides.yaml +++ b/sqflite/pubspec_overrides.yaml @@ -1,3 +1,3 @@ dependency_overrides: sentry: - path: ../dart + path: ../packages/dart From 773465786f801036687a1d84ea0616168a825595 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 30 Jul 2025 13:52:17 +0200 Subject: [PATCH 04/45] Update] --- .gitignore | 1 - packages/dart/AUTHORS | 8 + packages/dart/CHANGELOG.md | 1 + packages/dart/LICENSE | 27 + packages/dart/PATENTS | 17 + packages/dart/README.md | 194 + packages/dart/analysis_options.yaml | 25 + packages/dart/class-diagram.svg | 3364 +++++++++++++++++ packages/dart/dartdoc_options.yaml | 3 + packages/dart/example/.gitignore | 6 + packages/dart/example/README.md | 14 + packages/dart/example/analysis_options.yaml | 5 + packages/dart/example/bin/event_example.dart | 77 + packages/dart/example/bin/example.dart | 110 + packages/dart/example/pubspec.yaml | 15 + packages/dart/example/pubspec_overrides.yaml | 4 + packages/dart/example_web/.gitignore | 9 + packages/dart/example_web/README.md | 8 + .../dart/example_web/analysis_options.yaml | 5 + packages/dart/example_web/pubspec.yaml | 18 + .../dart/example_web/pubspec_overrides.yaml | 4 + packages/dart/example_web/web/event.dart | 76 + packages/dart/example_web/web/favicon.ico | Bin 0 -> 3559 bytes packages/dart/example_web/web/index.html | 69 + packages/dart/example_web/web/main.dart | 150 + packages/dart/example_web/web/styles.css | 14 + packages/dart/lib/sentry.dart | 63 + packages/dart/lib/sentry_io.dart | 6 + .../lib/src/client_reports/client_report.dart | 30 + .../client_report_recorder.dart | 55 + .../src/client_reports/discard_reason.dart | 15 + .../src/client_reports/discarded_event.dart | 75 + .../noop_client_report_recorder.dart | 20 + packages/dart/lib/src/constants.dart | 30 + .../src/dart_exception_type_identifier.dart | 42 + .../dart_exception_type_identifier_io.dart | 14 + .../dart_exception_type_identifier_web.dart | 6 + packages/dart/lib/src/diagnostic_log.dart | 34 + .../_io_environment_variables.dart | 24 + .../_web_environment_variables.dart | 26 + .../environment/environment_variables.dart | 43 + packages/dart/lib/src/environment/keys.dart | 6 + packages/dart/lib/src/event_processor.dart | 14 + .../deduplication_event_processor.dart | 70 + .../enricher/enricher_event_processor.dart | 9 + .../enricher/flutter_runtime.dart | 76 + .../enricher/io_enricher_event_processor.dart | 142 + .../enricher/io_platform_memory.dart | 143 + .../web_enricher_event_processor.dart | 122 + .../exception/exception_event_processor.dart | 9 + .../exception_group_event_processor.dart | 68 + .../io_exception_event_processor.dart | 127 + .../web_exception_event_processor.dart | 12 + .../event_processor/run_event_processors.dart | 79 + packages/dart/lib/src/exception_cause.dart | 8 + .../lib/src/exception_cause_extractor.dart | 35 + .../src/exception_stacktrace_extractor.dart | 35 + .../lib/src/exception_type_identifier.dart | 54 + .../lib/src/feature_flags_integration.dart | 45 + packages/dart/lib/src/hint.dart | 120 + .../src/http_client/breadcrumb_client.dart | 110 + .../lib/src/http_client/client_provider.dart | 16 + .../http_client/failed_request_client.dart | 254 ++ .../src/http_client/io_client_provider.dart | 67 + .../src/http_client/sentry_http_client.dart | 160 + .../http_client/sentry_http_client_error.dart | 7 + .../lib/src/http_client/tracing_client.dart | 80 + packages/dart/lib/src/hub.dart | 784 ++++ packages/dart/lib/src/hub_adapter.dart | 219 ++ packages/dart/lib/src/integration.dart | 14 + ...invalid_sentry_trace_header_exception.dart | 7 + .../lib/src/isolate_error_integration.dart | 25 + .../load_dart_debug_images_integration.dart | 171 + .../lib/src/logs_enricher_integration.dart | 37 + packages/dart/lib/src/noop_client.dart | 62 + packages/dart/lib/src/noop_hub.dart | 159 + .../src/noop_isolate_error_integration.dart | 15 + packages/dart/lib/src/noop_log_batcher.dart | 12 + packages/dart/lib/src/noop_sentry_client.dart | 79 + packages/dart/lib/src/noop_sentry_span.dart | 103 + packages/dart/lib/src/origin.dart | 1 + packages/dart/lib/src/origin_io.dart | 2 + packages/dart/lib/src/origin_web.dart | 4 + .../dart/lib/src/performance_collector.dart | 13 + .../dart/lib/src/platform/_io_platform.dart | 33 + .../dart/lib/src/platform/_web_platform.dart | 45 + .../dart/lib/src/platform/mock_platform.dart | 52 + packages/dart/lib/src/platform/platform.dart | 30 + packages/dart/lib/src/profiling.dart | 22 + .../dart/lib/src/propagation_context.dart | 57 + packages/dart/lib/src/protocol.dart | 46 + .../lib/src/protocol/access_aware_map.dart | 53 + .../dart/lib/src/protocol/breadcrumb.dart | 222 ++ packages/dart/lib/src/protocol/contexts.dart | 349 ++ .../dart/lib/src/protocol/debug_image.dart | 148 + .../dart/lib/src/protocol/debug_meta.dart | 69 + packages/dart/lib/src/protocol/dsn.dart | 68 + .../dart/lib/src/protocol/max_body_size.dart | 73 + packages/dart/lib/src/protocol/mechanism.dart | 178 + packages/dart/lib/src/protocol/sdk_info.dart | 60 + .../dart/lib/src/protocol/sdk_version.dart | 131 + .../dart/lib/src/protocol/sentry_app.dart | 148 + .../src/protocol/sentry_baggage_header.dart | 18 + .../dart/lib/src/protocol/sentry_browser.dart | 60 + .../dart/lib/src/protocol/sentry_culture.dart | 89 + .../dart/lib/src/protocol/sentry_device.dart | 401 ++ .../dart/lib/src/protocol/sentry_event.dart | 420 ++ .../lib/src/protocol/sentry_exception.dart | 112 + .../lib/src/protocol/sentry_feature_flag.dart | 51 + .../src/protocol/sentry_feature_flags.dart | 53 + .../lib/src/protocol/sentry_feedback.dart | 82 + .../dart/lib/src/protocol/sentry_geo.dart | 29 + .../dart/lib/src/protocol/sentry_gpu.dart | 202 + packages/dart/lib/src/protocol/sentry_id.dart | 40 + .../dart/lib/src/protocol/sentry_level.dart | 57 + .../dart/lib/src/protocol/sentry_log.dart | 35 + .../src/protocol/sentry_log_attribute.dart | 30 + .../lib/src/protocol/sentry_log_level.dart | 28 + .../dart/lib/src/protocol/sentry_message.dart | 70 + .../src/protocol/sentry_operating_system.dart | 130 + .../dart/lib/src/protocol/sentry_package.dart | 48 + .../dart/lib/src/protocol/sentry_proxy.dart | 63 + .../dart/lib/src/protocol/sentry_request.dart | 178 + .../lib/src/protocol/sentry_response.dart | 106 + .../dart/lib/src/protocol/sentry_runtime.dart | 105 + .../dart/lib/src/protocol/sentry_span.dart | 244 ++ .../lib/src/protocol/sentry_stack_frame.dart | 237 ++ .../lib/src/protocol/sentry_stack_trace.dart | 105 + .../dart/lib/src/protocol/sentry_thread.dart | 82 + .../src/protocol/sentry_trace_context.dart | 128 + .../lib/src/protocol/sentry_trace_header.dart | 51 + .../lib/src/protocol/sentry_transaction.dart | 155 + .../src/protocol/sentry_transaction_info.dart | 37 + .../sentry_transaction_name_source.dart | 19 + .../dart/lib/src/protocol/sentry_user.dart | 164 + .../src/protocol/sentry_view_hierarchy.dart | 20 + .../sentry_view_hierarchy_element.dart | 55 + packages/dart/lib/src/protocol/span_id.dart | 38 + .../dart/lib/src/protocol/span_status.dart | 145 + .../recursive_exception_cause_extractor.dart | 54 + .../src/run_zoned_guarded_integration.dart | 40 + packages/dart/lib/src/runtime_checker.dart | 51 + packages/dart/lib/src/scope.dart | 474 +++ packages/dart/lib/src/scope_observer.dart | 16 + .../dart/lib/src/sdk_lifecycle_hooks.dart | 73 + packages/dart/lib/src/sentry.dart | 438 +++ .../io_sentry_attachment.dart | 33 + .../sentry_attachment/sentry_attachment.dart | 128 + packages/dart/lib/src/sentry_baggage.dart | 222 ++ packages/dart/lib/src/sentry_client.dart | 688 ++++ packages/dart/lib/src/sentry_envelope.dart | 137 + .../dart/lib/src/sentry_envelope_header.dart | 64 + .../dart/lib/src/sentry_envelope_item.dart | 86 + .../lib/src/sentry_envelope_item_header.dart | 33 + packages/dart/lib/src/sentry_event_like.dart | 7 + .../lib/src/sentry_exception_factory.dart | 87 + packages/dart/lib/src/sentry_isolate.dart | 93 + .../lib/src/sentry_isolate_extension.dart | 29 + packages/dart/lib/src/sentry_item_type.dart | 10 + packages/dart/lib/src/sentry_log_batcher.dart | 52 + packages/dart/lib/src/sentry_logger.dart | 75 + .../dart/lib/src/sentry_logger_formatter.dart | 147 + packages/dart/lib/src/sentry_measurement.dart | 76 + .../dart/lib/src/sentry_measurement_unit.dart | 122 + packages/dart/lib/src/sentry_options.dart | 682 ++++ .../lib/src/sentry_run_zoned_guarded.dart | 118 + .../dart/lib/src/sentry_sampling_context.dart | 21 + .../dart/lib/src/sentry_span_context.dart | 69 + .../dart/lib/src/sentry_span_interface.dart | 90 + .../lib/src/sentry_stack_trace_factory.dart | 222 ++ .../dart/lib/src/sentry_template_string.dart | 43 + .../lib/src/sentry_trace_context_header.dart | 117 + .../dart/lib/src/sentry_trace_origins.dart | 30 + packages/dart/lib/src/sentry_tracer.dart | 438 +++ .../lib/src/sentry_tracer_finish_status.dart | 15 + .../dart/lib/src/sentry_traces_sampler.dart | 87 + .../src/sentry_traces_sampling_decision.dart | 11 + .../lib/src/sentry_transaction_context.dart | 81 + .../dart/lib/src/span_data_convention.dart | 10 + packages/dart/lib/src/spotlight.dart | 12 + .../dart/lib/src/throwable_mechanism.dart | 20 + packages/dart/lib/src/tracing.dart | 10 + .../transport/client_report_transport.dart | 56 + .../dart/lib/src/transport/data_category.dart | 38 + packages/dart/lib/src/transport/encode.dart | 14 + .../lib/src/transport/http_transport.dart | 87 + .../http_transport_request_handler.dart | 98 + .../dart/lib/src/transport/noop_encode.dart | 7 + .../lib/src/transport/noop_transport.dart | 11 + .../dart/lib/src/transport/rate_limit.dart | 11 + .../lib/src/transport/rate_limit_parser.dart | 95 + .../dart/lib/src/transport/rate_limiter.dart | 124 + .../transport/spotlight_http_transport.dart | 59 + .../dart/lib/src/transport/task_queue.dart | 61 + .../dart/lib/src/transport/transport.dart | 10 + packages/dart/lib/src/type_check_hint.dart | 26 + packages/dart/lib/src/utils.dart | 36 + .../lib/src/utils/_io_get_isolate_name.dart | 3 + .../_io_get_sentry_operating_system.dart | 72 + .../lib/src/utils/_web_get_isolate_name.dart | 1 + .../_web_get_sentry_operating_system.dart | 13 + .../lib/src/utils/breadcrumb_log_level.dart | 16 + packages/dart/lib/src/utils/crc32_utils.dart | 313 ++ .../dart/lib/src/utils/http_header_utils.dart | 16 + .../dart/lib/src/utils/http_sanitizer.dart | 91 + .../dart/lib/src/utils/isolate_utils.dart | 7 + .../dart/lib/src/utils/iterable_utils.dart | 17 + packages/dart/lib/src/utils/os_utils.dart | 14 + packages/dart/lib/src/utils/regex_utils.dart | 9 + .../lib/src/utils/sample_rate_format.dart | 324 ++ .../dart/lib/src/utils/stacktrace_utils.dart | 18 + .../dart/lib/src/utils/tracing_utils.dart | 128 + .../dart/lib/src/utils/transport_utils.dart | 45 + packages/dart/lib/src/utils/url_details.dart | 32 + packages/dart/lib/src/version.dart | 33 + packages/dart/pubspec.yaml | 38 + .../client_report_recorder_test.dart | 94 + .../client_reports/client_report_test.dart | 54 + packages/dart/test/contexts_test.dart | 331 ++ .../dart/test/diagnostic_logger_test.dart | 68 + packages/dart/test/environment_test.dart | 59 + .../deduplication_event_processor_test.dart | 123 + .../enricher/io_enricher_test.dart | 268 ++ .../enricher/io_platform_memory_test.dart | 43 + .../enricher/web_enricher_test.dart | 207 + .../exception_group_event_processor_test.dart | 453 +++ .../io_exception_event_processor_test.dart | 132 + .../dart/test/example_web_compile_test.dart | 91 + .../dart/test/exception_identifier_test.dart | 186 + .../test/feature_flags_integration_test.dart | 117 + packages/dart/test/hint_test.dart | 108 + .../http_client/breadcrumb_client_test.dart | 301 ++ .../failed_request_client_test.dart | 405 ++ .../http_client/io_client_provider_test.dart | 267 ++ .../http_client/sentry_http_client_test.dart | 201 + .../test/http_client/tracing_client_test.dart | 299 ++ packages/dart/test/hub_test.dart | 925 +++++ packages/dart/test/initialization_test.dart | 57 + ...ad_dart_debug_images_integration_test.dart | 310 ++ .../test/logs_enricher_interation_test.dart | 99 + packages/dart/test/mocks.dart | 194 + packages/dart/test/mocks.mocks.dart | 115 + .../mocks/mock_client_report_recorder.dart | 25 + packages/dart/test/mocks/mock_envelope.dart | 29 + .../mocks/mock_environment_variables.dart | 33 + packages/dart/test/mocks/mock_hub.dart | 196 + .../dart/test/mocks/mock_integration.dart | 20 + .../dart/test/mocks/mock_log_batcher.dart | 19 + .../dart/test/mocks/mock_runtime_checker.dart | 35 + .../dart/test/mocks/mock_scope_observer.dart | 79 + .../dart/test/mocks/mock_sentry_client.dart | 188 + packages/dart/test/mocks/mock_transport.dart | 78 + .../test/mocks/no_such_method_provider.dart | 7 + .../dart/test/propagation_context_test.dart | 157 + .../test/protocol/access_aware_map_tests.dart | 118 + .../dart/test/protocol/breadcrumb_test.dart | 229 ++ .../dart/test/protocol/contexts_test.dart | 127 + packages/dart/test/protocol/culture_test.dart | 55 + .../dart/test/protocol/debug_image_test.dart | 94 + .../dart/test/protocol/debug_meta_test.dart | 43 + .../dart/test/protocol/mechanism_test.dart | 97 + .../test/protocol/rate_limit_parser_test.dart | 146 + .../dart/test/protocol/rate_limiter_test.dart | 350 ++ .../dart/test/protocol/sdk_info_test.dart | 74 + .../dart/test/protocol/sdk_version_test.dart | 111 + .../dart/test/protocol/sentry_app_test.dart | 112 + .../protocol/sentry_baggage_header_test.dart | 43 + .../test/protocol/sentry_browser_test.dart | 60 + .../test/protocol/sentry_device_test.dart | 350 ++ .../test/protocol/sentry_exception_test.dart | 158 + .../protocol/sentry_feature_flag_tests.dart | 38 + .../protocol/sentry_feature_flags_tests.dart | 42 + .../test/protocol/sentry_feedback_test.dart | 85 + .../dart/test/protocol/sentry_gpu_test.dart | 91 + .../protocol/sentry_log_attribute_test.dart | 42 + .../dart/test/protocol/sentry_log_test.dart | 86 + .../test/protocol/sentry_message_test.dart | 68 + .../sentry_operating_system_test.dart | 79 + .../test/protocol/sentry_package_test.dart | 63 + .../dart/test/protocol/sentry_proxy_test.dart | 102 + .../test/protocol/sentry_request_test.dart | 92 + .../test/protocol/sentry_response_test.dart | 64 + .../test/protocol/sentry_runtime_test.dart | 71 + .../protocol/sentry_stack_frame_test.dart | 127 + .../protocol/sentry_stack_trace_test.dart | 76 + .../protocol/sentry_trace_header_test.dart | 41 + .../sentry_transaction_info_test.dart | 29 + .../dart/test/protocol/sentry_user_test.dart | 98 + .../sentry_view_hierarchy_element_test.dart | 65 + .../protocol/sentry_view_hierarchy_test.dart | 52 + packages/dart/test/protocol/span_id_test.dart | 23 + .../dart/test/protocol/span_status_test.dart | 138 + ...ursive_exception_cause_extractor_test.dart | 189 + packages/dart/test/scope_test.dart | 920 +++++ .../dart/test/sdk_lifecycle_hooks_test.dart | 111 + .../dart/test/sentry_attachment_io_test.dart | 32 + .../dart/test/sentry_attachment_test.dart | 192 + packages/dart/test/sentry_baggage_test.dart | 97 + packages/dart/test/sentry_browser_test.dart | 13 + .../test/sentry_client_lifecycle_test.dart | 195 + .../sentry_client_sdk_lifecycle_test.dart | 195 + packages/dart/test/sentry_client_test.dart | 2833 ++++++++++++++ .../test/sentry_envelope_header_test.dart | 44 + .../sentry_envelope_item_header_test.dart | 19 + .../dart/test/sentry_envelope_item_test.dart | 136 + packages/dart/test/sentry_envelope_test.dart | 276 ++ .../dart/test/sentry_envelope_vm_test.dart | 78 + packages/dart/test/sentry_event_test.dart | 500 +++ .../test/sentry_exception_factory_test.dart | 355 ++ packages/dart/test/sentry_id_test.dart | 35 + packages/dart/test/sentry_io_client_test.dart | 15 + .../test/sentry_isolate_extension_test.dart | 60 + packages/dart/test/sentry_isolate_test.dart | 75 + .../dart/test/sentry_log_batcher_test.dart | 148 + .../test/sentry_logger_formatter_test.dart | 307 ++ packages/dart/test/sentry_logger_test.dart | 95 + .../dart/test/sentry_measurement_test.dart | 42 + .../test/sentry_measurement_unit_test.dart | 130 + packages/dart/test/sentry_options_test.dart | 186 + .../test/sentry_run_zoned_guarded_test.dart | 123 + .../dart/test/sentry_span_context_test.dart | 47 + packages/dart/test/sentry_span_test.dart | 316 ++ .../test/sentry_template_string_test.dart | 153 + packages/dart/test/sentry_test.dart | 683 ++++ .../sentry_trace_context_header_test.dart | 72 + .../dart/test/sentry_trace_context_test.dart | 82 + packages/dart/test/sentry_tracer_test.dart | 637 ++++ .../dart/test/sentry_traces_sampler_test.dart | 150 + .../test/sentry_transaction_context_test.dart | 76 + .../dart/test/sentry_transaction_test.dart | 104 + packages/dart/test/stack_trace_test.dart | 352 ++ packages/dart/test/test_utils.dart | 459 +++ .../client_report_transport_test.dart | 169 + .../test/transport/http_transport_test.dart | 306 ++ .../spotlight_http_transport_test.dart | 69 + .../dart/test/transport/tesk_queue_test.dart | 145 + .../test/unsupported_throwables_test.dart | 92 + .../test/utils/http_header_utils_test.dart | 18 + .../dart/test/utils/http_sanitizer_test.dart | 189 + .../dart/test/utils/regex_utils_test.dart | 24 + .../test/utils/sample_rate_format_test.dart | 49 + .../dart/test/utils/tracing_utils_test.dart | 357 ++ .../dart/test/utils/url_details_test.dart | 93 + packages/dart/test/utils_test.dart | 34 + packages/dart/test/version_test.dart | 22 + .../envelope-no-attachment.envelope | 3 + .../envelope-with-image.envelope | Bin 0 -> 3712 bytes packages/dart/test_resources/sentry.png | Bin 0 -> 3535 bytes packages/dart/test_resources/testfile.txt | 1 + packages/dart/tool/presubmit.sh | 19 + 350 files changed, 43420 insertions(+), 1 deletion(-) create mode 100644 packages/dart/AUTHORS create mode 120000 packages/dart/CHANGELOG.md create mode 100644 packages/dart/LICENSE create mode 100644 packages/dart/PATENTS create mode 100644 packages/dart/README.md create mode 100644 packages/dart/analysis_options.yaml create mode 100644 packages/dart/class-diagram.svg create mode 100644 packages/dart/dartdoc_options.yaml create mode 100644 packages/dart/example/.gitignore create mode 100644 packages/dart/example/README.md create mode 100644 packages/dart/example/analysis_options.yaml create mode 100644 packages/dart/example/bin/event_example.dart create mode 100644 packages/dart/example/bin/example.dart create mode 100644 packages/dart/example/pubspec.yaml create mode 100644 packages/dart/example/pubspec_overrides.yaml create mode 100644 packages/dart/example_web/.gitignore create mode 100644 packages/dart/example_web/README.md create mode 100644 packages/dart/example_web/analysis_options.yaml create mode 100644 packages/dart/example_web/pubspec.yaml create mode 100644 packages/dart/example_web/pubspec_overrides.yaml create mode 100644 packages/dart/example_web/web/event.dart create mode 100644 packages/dart/example_web/web/favicon.ico create mode 100644 packages/dart/example_web/web/index.html create mode 100644 packages/dart/example_web/web/main.dart create mode 100644 packages/dart/example_web/web/styles.css create mode 100644 packages/dart/lib/sentry.dart create mode 100644 packages/dart/lib/sentry_io.dart create mode 100644 packages/dart/lib/src/client_reports/client_report.dart create mode 100644 packages/dart/lib/src/client_reports/client_report_recorder.dart create mode 100644 packages/dart/lib/src/client_reports/discard_reason.dart create mode 100644 packages/dart/lib/src/client_reports/discarded_event.dart create mode 100644 packages/dart/lib/src/client_reports/noop_client_report_recorder.dart create mode 100644 packages/dart/lib/src/constants.dart create mode 100644 packages/dart/lib/src/dart_exception_type_identifier.dart create mode 100644 packages/dart/lib/src/dart_exception_type_identifier_io.dart create mode 100644 packages/dart/lib/src/dart_exception_type_identifier_web.dart create mode 100644 packages/dart/lib/src/diagnostic_log.dart create mode 100644 packages/dart/lib/src/environment/_io_environment_variables.dart create mode 100644 packages/dart/lib/src/environment/_web_environment_variables.dart create mode 100644 packages/dart/lib/src/environment/environment_variables.dart create mode 100644 packages/dart/lib/src/environment/keys.dart create mode 100644 packages/dart/lib/src/event_processor.dart create mode 100644 packages/dart/lib/src/event_processor/deduplication_event_processor.dart create mode 100644 packages/dart/lib/src/event_processor/enricher/enricher_event_processor.dart create mode 100644 packages/dart/lib/src/event_processor/enricher/flutter_runtime.dart create mode 100644 packages/dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart create mode 100644 packages/dart/lib/src/event_processor/enricher/io_platform_memory.dart create mode 100644 packages/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart create mode 100644 packages/dart/lib/src/event_processor/exception/exception_event_processor.dart create mode 100644 packages/dart/lib/src/event_processor/exception/exception_group_event_processor.dart create mode 100644 packages/dart/lib/src/event_processor/exception/io_exception_event_processor.dart create mode 100644 packages/dart/lib/src/event_processor/exception/web_exception_event_processor.dart create mode 100644 packages/dart/lib/src/event_processor/run_event_processors.dart create mode 100644 packages/dart/lib/src/exception_cause.dart create mode 100644 packages/dart/lib/src/exception_cause_extractor.dart create mode 100644 packages/dart/lib/src/exception_stacktrace_extractor.dart create mode 100644 packages/dart/lib/src/exception_type_identifier.dart create mode 100644 packages/dart/lib/src/feature_flags_integration.dart create mode 100644 packages/dart/lib/src/hint.dart create mode 100644 packages/dart/lib/src/http_client/breadcrumb_client.dart create mode 100644 packages/dart/lib/src/http_client/client_provider.dart create mode 100644 packages/dart/lib/src/http_client/failed_request_client.dart create mode 100644 packages/dart/lib/src/http_client/io_client_provider.dart create mode 100644 packages/dart/lib/src/http_client/sentry_http_client.dart create mode 100644 packages/dart/lib/src/http_client/sentry_http_client_error.dart create mode 100644 packages/dart/lib/src/http_client/tracing_client.dart create mode 100644 packages/dart/lib/src/hub.dart create mode 100644 packages/dart/lib/src/hub_adapter.dart create mode 100644 packages/dart/lib/src/integration.dart create mode 100644 packages/dart/lib/src/invalid_sentry_trace_header_exception.dart create mode 100644 packages/dart/lib/src/isolate_error_integration.dart create mode 100644 packages/dart/lib/src/load_dart_debug_images_integration.dart create mode 100644 packages/dart/lib/src/logs_enricher_integration.dart create mode 100644 packages/dart/lib/src/noop_client.dart create mode 100644 packages/dart/lib/src/noop_hub.dart create mode 100644 packages/dart/lib/src/noop_isolate_error_integration.dart create mode 100644 packages/dart/lib/src/noop_log_batcher.dart create mode 100644 packages/dart/lib/src/noop_sentry_client.dart create mode 100644 packages/dart/lib/src/noop_sentry_span.dart create mode 100644 packages/dart/lib/src/origin.dart create mode 100644 packages/dart/lib/src/origin_io.dart create mode 100644 packages/dart/lib/src/origin_web.dart create mode 100644 packages/dart/lib/src/performance_collector.dart create mode 100644 packages/dart/lib/src/platform/_io_platform.dart create mode 100644 packages/dart/lib/src/platform/_web_platform.dart create mode 100644 packages/dart/lib/src/platform/mock_platform.dart create mode 100644 packages/dart/lib/src/platform/platform.dart create mode 100644 packages/dart/lib/src/profiling.dart create mode 100644 packages/dart/lib/src/propagation_context.dart create mode 100644 packages/dart/lib/src/protocol.dart create mode 100644 packages/dart/lib/src/protocol/access_aware_map.dart create mode 100644 packages/dart/lib/src/protocol/breadcrumb.dart create mode 100644 packages/dart/lib/src/protocol/contexts.dart create mode 100644 packages/dart/lib/src/protocol/debug_image.dart create mode 100644 packages/dart/lib/src/protocol/debug_meta.dart create mode 100644 packages/dart/lib/src/protocol/dsn.dart create mode 100644 packages/dart/lib/src/protocol/max_body_size.dart create mode 100644 packages/dart/lib/src/protocol/mechanism.dart create mode 100644 packages/dart/lib/src/protocol/sdk_info.dart create mode 100644 packages/dart/lib/src/protocol/sdk_version.dart create mode 100644 packages/dart/lib/src/protocol/sentry_app.dart create mode 100644 packages/dart/lib/src/protocol/sentry_baggage_header.dart create mode 100644 packages/dart/lib/src/protocol/sentry_browser.dart create mode 100644 packages/dart/lib/src/protocol/sentry_culture.dart create mode 100644 packages/dart/lib/src/protocol/sentry_device.dart create mode 100644 packages/dart/lib/src/protocol/sentry_event.dart create mode 100644 packages/dart/lib/src/protocol/sentry_exception.dart create mode 100644 packages/dart/lib/src/protocol/sentry_feature_flag.dart create mode 100644 packages/dart/lib/src/protocol/sentry_feature_flags.dart create mode 100644 packages/dart/lib/src/protocol/sentry_feedback.dart create mode 100644 packages/dart/lib/src/protocol/sentry_geo.dart create mode 100644 packages/dart/lib/src/protocol/sentry_gpu.dart create mode 100644 packages/dart/lib/src/protocol/sentry_id.dart create mode 100644 packages/dart/lib/src/protocol/sentry_level.dart create mode 100644 packages/dart/lib/src/protocol/sentry_log.dart create mode 100644 packages/dart/lib/src/protocol/sentry_log_attribute.dart create mode 100644 packages/dart/lib/src/protocol/sentry_log_level.dart create mode 100644 packages/dart/lib/src/protocol/sentry_message.dart create mode 100644 packages/dart/lib/src/protocol/sentry_operating_system.dart create mode 100644 packages/dart/lib/src/protocol/sentry_package.dart create mode 100644 packages/dart/lib/src/protocol/sentry_proxy.dart create mode 100644 packages/dart/lib/src/protocol/sentry_request.dart create mode 100644 packages/dart/lib/src/protocol/sentry_response.dart create mode 100644 packages/dart/lib/src/protocol/sentry_runtime.dart create mode 100644 packages/dart/lib/src/protocol/sentry_span.dart create mode 100644 packages/dart/lib/src/protocol/sentry_stack_frame.dart create mode 100644 packages/dart/lib/src/protocol/sentry_stack_trace.dart create mode 100644 packages/dart/lib/src/protocol/sentry_thread.dart create mode 100644 packages/dart/lib/src/protocol/sentry_trace_context.dart create mode 100644 packages/dart/lib/src/protocol/sentry_trace_header.dart create mode 100644 packages/dart/lib/src/protocol/sentry_transaction.dart create mode 100644 packages/dart/lib/src/protocol/sentry_transaction_info.dart create mode 100644 packages/dart/lib/src/protocol/sentry_transaction_name_source.dart create mode 100644 packages/dart/lib/src/protocol/sentry_user.dart create mode 100644 packages/dart/lib/src/protocol/sentry_view_hierarchy.dart create mode 100644 packages/dart/lib/src/protocol/sentry_view_hierarchy_element.dart create mode 100644 packages/dart/lib/src/protocol/span_id.dart create mode 100644 packages/dart/lib/src/protocol/span_status.dart create mode 100644 packages/dart/lib/src/recursive_exception_cause_extractor.dart create mode 100644 packages/dart/lib/src/run_zoned_guarded_integration.dart create mode 100644 packages/dart/lib/src/runtime_checker.dart create mode 100644 packages/dart/lib/src/scope.dart create mode 100644 packages/dart/lib/src/scope_observer.dart create mode 100644 packages/dart/lib/src/sdk_lifecycle_hooks.dart create mode 100644 packages/dart/lib/src/sentry.dart create mode 100644 packages/dart/lib/src/sentry_attachment/io_sentry_attachment.dart create mode 100644 packages/dart/lib/src/sentry_attachment/sentry_attachment.dart create mode 100644 packages/dart/lib/src/sentry_baggage.dart create mode 100644 packages/dart/lib/src/sentry_client.dart create mode 100644 packages/dart/lib/src/sentry_envelope.dart create mode 100644 packages/dart/lib/src/sentry_envelope_header.dart create mode 100644 packages/dart/lib/src/sentry_envelope_item.dart create mode 100644 packages/dart/lib/src/sentry_envelope_item_header.dart create mode 100644 packages/dart/lib/src/sentry_event_like.dart create mode 100644 packages/dart/lib/src/sentry_exception_factory.dart create mode 100644 packages/dart/lib/src/sentry_isolate.dart create mode 100644 packages/dart/lib/src/sentry_isolate_extension.dart create mode 100644 packages/dart/lib/src/sentry_item_type.dart create mode 100644 packages/dart/lib/src/sentry_log_batcher.dart create mode 100644 packages/dart/lib/src/sentry_logger.dart create mode 100644 packages/dart/lib/src/sentry_logger_formatter.dart create mode 100644 packages/dart/lib/src/sentry_measurement.dart create mode 100644 packages/dart/lib/src/sentry_measurement_unit.dart create mode 100644 packages/dart/lib/src/sentry_options.dart create mode 100644 packages/dart/lib/src/sentry_run_zoned_guarded.dart create mode 100644 packages/dart/lib/src/sentry_sampling_context.dart create mode 100644 packages/dart/lib/src/sentry_span_context.dart create mode 100644 packages/dart/lib/src/sentry_span_interface.dart create mode 100644 packages/dart/lib/src/sentry_stack_trace_factory.dart create mode 100644 packages/dart/lib/src/sentry_template_string.dart create mode 100644 packages/dart/lib/src/sentry_trace_context_header.dart create mode 100644 packages/dart/lib/src/sentry_trace_origins.dart create mode 100644 packages/dart/lib/src/sentry_tracer.dart create mode 100644 packages/dart/lib/src/sentry_tracer_finish_status.dart create mode 100644 packages/dart/lib/src/sentry_traces_sampler.dart create mode 100644 packages/dart/lib/src/sentry_traces_sampling_decision.dart create mode 100644 packages/dart/lib/src/sentry_transaction_context.dart create mode 100644 packages/dart/lib/src/span_data_convention.dart create mode 100644 packages/dart/lib/src/spotlight.dart create mode 100644 packages/dart/lib/src/throwable_mechanism.dart create mode 100644 packages/dart/lib/src/tracing.dart create mode 100644 packages/dart/lib/src/transport/client_report_transport.dart create mode 100644 packages/dart/lib/src/transport/data_category.dart create mode 100644 packages/dart/lib/src/transport/encode.dart create mode 100644 packages/dart/lib/src/transport/http_transport.dart create mode 100644 packages/dart/lib/src/transport/http_transport_request_handler.dart create mode 100644 packages/dart/lib/src/transport/noop_encode.dart create mode 100644 packages/dart/lib/src/transport/noop_transport.dart create mode 100644 packages/dart/lib/src/transport/rate_limit.dart create mode 100644 packages/dart/lib/src/transport/rate_limit_parser.dart create mode 100644 packages/dart/lib/src/transport/rate_limiter.dart create mode 100644 packages/dart/lib/src/transport/spotlight_http_transport.dart create mode 100644 packages/dart/lib/src/transport/task_queue.dart create mode 100644 packages/dart/lib/src/transport/transport.dart create mode 100644 packages/dart/lib/src/type_check_hint.dart create mode 100644 packages/dart/lib/src/utils.dart create mode 100644 packages/dart/lib/src/utils/_io_get_isolate_name.dart create mode 100644 packages/dart/lib/src/utils/_io_get_sentry_operating_system.dart create mode 100644 packages/dart/lib/src/utils/_web_get_isolate_name.dart create mode 100644 packages/dart/lib/src/utils/_web_get_sentry_operating_system.dart create mode 100644 packages/dart/lib/src/utils/breadcrumb_log_level.dart create mode 100644 packages/dart/lib/src/utils/crc32_utils.dart create mode 100644 packages/dart/lib/src/utils/http_header_utils.dart create mode 100644 packages/dart/lib/src/utils/http_sanitizer.dart create mode 100644 packages/dart/lib/src/utils/isolate_utils.dart create mode 100644 packages/dart/lib/src/utils/iterable_utils.dart create mode 100644 packages/dart/lib/src/utils/os_utils.dart create mode 100644 packages/dart/lib/src/utils/regex_utils.dart create mode 100644 packages/dart/lib/src/utils/sample_rate_format.dart create mode 100644 packages/dart/lib/src/utils/stacktrace_utils.dart create mode 100644 packages/dart/lib/src/utils/tracing_utils.dart create mode 100644 packages/dart/lib/src/utils/transport_utils.dart create mode 100644 packages/dart/lib/src/utils/url_details.dart create mode 100644 packages/dart/lib/src/version.dart create mode 100644 packages/dart/pubspec.yaml create mode 100644 packages/dart/test/client_reports/client_report_recorder_test.dart create mode 100644 packages/dart/test/client_reports/client_report_test.dart create mode 100644 packages/dart/test/contexts_test.dart create mode 100644 packages/dart/test/diagnostic_logger_test.dart create mode 100644 packages/dart/test/environment_test.dart create mode 100644 packages/dart/test/event_processor/deduplication_event_processor_test.dart create mode 100644 packages/dart/test/event_processor/enricher/io_enricher_test.dart create mode 100644 packages/dart/test/event_processor/enricher/io_platform_memory_test.dart create mode 100644 packages/dart/test/event_processor/enricher/web_enricher_test.dart create mode 100644 packages/dart/test/event_processor/exception/exception_group_event_processor_test.dart create mode 100644 packages/dart/test/event_processor/exception/io_exception_event_processor_test.dart create mode 100644 packages/dart/test/example_web_compile_test.dart create mode 100644 packages/dart/test/exception_identifier_test.dart create mode 100644 packages/dart/test/feature_flags_integration_test.dart create mode 100644 packages/dart/test/hint_test.dart create mode 100644 packages/dart/test/http_client/breadcrumb_client_test.dart create mode 100644 packages/dart/test/http_client/failed_request_client_test.dart create mode 100644 packages/dart/test/http_client/io_client_provider_test.dart create mode 100644 packages/dart/test/http_client/sentry_http_client_test.dart create mode 100644 packages/dart/test/http_client/tracing_client_test.dart create mode 100644 packages/dart/test/hub_test.dart create mode 100644 packages/dart/test/initialization_test.dart create mode 100644 packages/dart/test/load_dart_debug_images_integration_test.dart create mode 100644 packages/dart/test/logs_enricher_interation_test.dart create mode 100644 packages/dart/test/mocks.dart create mode 100644 packages/dart/test/mocks.mocks.dart create mode 100644 packages/dart/test/mocks/mock_client_report_recorder.dart create mode 100644 packages/dart/test/mocks/mock_envelope.dart create mode 100644 packages/dart/test/mocks/mock_environment_variables.dart create mode 100644 packages/dart/test/mocks/mock_hub.dart create mode 100644 packages/dart/test/mocks/mock_integration.dart create mode 100644 packages/dart/test/mocks/mock_log_batcher.dart create mode 100644 packages/dart/test/mocks/mock_runtime_checker.dart create mode 100644 packages/dart/test/mocks/mock_scope_observer.dart create mode 100644 packages/dart/test/mocks/mock_sentry_client.dart create mode 100644 packages/dart/test/mocks/mock_transport.dart create mode 100644 packages/dart/test/mocks/no_such_method_provider.dart create mode 100644 packages/dart/test/propagation_context_test.dart create mode 100644 packages/dart/test/protocol/access_aware_map_tests.dart create mode 100644 packages/dart/test/protocol/breadcrumb_test.dart create mode 100644 packages/dart/test/protocol/contexts_test.dart create mode 100644 packages/dart/test/protocol/culture_test.dart create mode 100644 packages/dart/test/protocol/debug_image_test.dart create mode 100644 packages/dart/test/protocol/debug_meta_test.dart create mode 100644 packages/dart/test/protocol/mechanism_test.dart create mode 100644 packages/dart/test/protocol/rate_limit_parser_test.dart create mode 100644 packages/dart/test/protocol/rate_limiter_test.dart create mode 100644 packages/dart/test/protocol/sdk_info_test.dart create mode 100644 packages/dart/test/protocol/sdk_version_test.dart create mode 100644 packages/dart/test/protocol/sentry_app_test.dart create mode 100644 packages/dart/test/protocol/sentry_baggage_header_test.dart create mode 100644 packages/dart/test/protocol/sentry_browser_test.dart create mode 100644 packages/dart/test/protocol/sentry_device_test.dart create mode 100644 packages/dart/test/protocol/sentry_exception_test.dart create mode 100644 packages/dart/test/protocol/sentry_feature_flag_tests.dart create mode 100644 packages/dart/test/protocol/sentry_feature_flags_tests.dart create mode 100644 packages/dart/test/protocol/sentry_feedback_test.dart create mode 100644 packages/dart/test/protocol/sentry_gpu_test.dart create mode 100644 packages/dart/test/protocol/sentry_log_attribute_test.dart create mode 100644 packages/dart/test/protocol/sentry_log_test.dart create mode 100644 packages/dart/test/protocol/sentry_message_test.dart create mode 100644 packages/dart/test/protocol/sentry_operating_system_test.dart create mode 100644 packages/dart/test/protocol/sentry_package_test.dart create mode 100644 packages/dart/test/protocol/sentry_proxy_test.dart create mode 100644 packages/dart/test/protocol/sentry_request_test.dart create mode 100644 packages/dart/test/protocol/sentry_response_test.dart create mode 100644 packages/dart/test/protocol/sentry_runtime_test.dart create mode 100644 packages/dart/test/protocol/sentry_stack_frame_test.dart create mode 100644 packages/dart/test/protocol/sentry_stack_trace_test.dart create mode 100644 packages/dart/test/protocol/sentry_trace_header_test.dart create mode 100644 packages/dart/test/protocol/sentry_transaction_info_test.dart create mode 100644 packages/dart/test/protocol/sentry_user_test.dart create mode 100644 packages/dart/test/protocol/sentry_view_hierarchy_element_test.dart create mode 100644 packages/dart/test/protocol/sentry_view_hierarchy_test.dart create mode 100644 packages/dart/test/protocol/span_id_test.dart create mode 100644 packages/dart/test/protocol/span_status_test.dart create mode 100644 packages/dart/test/recursive_exception_cause_extractor_test.dart create mode 100644 packages/dart/test/scope_test.dart create mode 100644 packages/dart/test/sdk_lifecycle_hooks_test.dart create mode 100644 packages/dart/test/sentry_attachment_io_test.dart create mode 100644 packages/dart/test/sentry_attachment_test.dart create mode 100644 packages/dart/test/sentry_baggage_test.dart create mode 100644 packages/dart/test/sentry_browser_test.dart create mode 100644 packages/dart/test/sentry_client_lifecycle_test.dart create mode 100644 packages/dart/test/sentry_client_sdk_lifecycle_test.dart create mode 100644 packages/dart/test/sentry_client_test.dart create mode 100644 packages/dart/test/sentry_envelope_header_test.dart create mode 100644 packages/dart/test/sentry_envelope_item_header_test.dart create mode 100644 packages/dart/test/sentry_envelope_item_test.dart create mode 100644 packages/dart/test/sentry_envelope_test.dart create mode 100644 packages/dart/test/sentry_envelope_vm_test.dart create mode 100644 packages/dart/test/sentry_event_test.dart create mode 100644 packages/dart/test/sentry_exception_factory_test.dart create mode 100644 packages/dart/test/sentry_id_test.dart create mode 100644 packages/dart/test/sentry_io_client_test.dart create mode 100644 packages/dart/test/sentry_isolate_extension_test.dart create mode 100644 packages/dart/test/sentry_isolate_test.dart create mode 100644 packages/dart/test/sentry_log_batcher_test.dart create mode 100644 packages/dart/test/sentry_logger_formatter_test.dart create mode 100644 packages/dart/test/sentry_logger_test.dart create mode 100644 packages/dart/test/sentry_measurement_test.dart create mode 100644 packages/dart/test/sentry_measurement_unit_test.dart create mode 100644 packages/dart/test/sentry_options_test.dart create mode 100644 packages/dart/test/sentry_run_zoned_guarded_test.dart create mode 100644 packages/dart/test/sentry_span_context_test.dart create mode 100644 packages/dart/test/sentry_span_test.dart create mode 100644 packages/dart/test/sentry_template_string_test.dart create mode 100644 packages/dart/test/sentry_test.dart create mode 100644 packages/dart/test/sentry_trace_context_header_test.dart create mode 100644 packages/dart/test/sentry_trace_context_test.dart create mode 100644 packages/dart/test/sentry_tracer_test.dart create mode 100644 packages/dart/test/sentry_traces_sampler_test.dart create mode 100644 packages/dart/test/sentry_transaction_context_test.dart create mode 100644 packages/dart/test/sentry_transaction_test.dart create mode 100644 packages/dart/test/stack_trace_test.dart create mode 100644 packages/dart/test/test_utils.dart create mode 100644 packages/dart/test/transport/client_report_transport_test.dart create mode 100644 packages/dart/test/transport/http_transport_test.dart create mode 100644 packages/dart/test/transport/spotlight_http_transport_test.dart create mode 100644 packages/dart/test/transport/tesk_queue_test.dart create mode 100644 packages/dart/test/unsupported_throwables_test.dart create mode 100644 packages/dart/test/utils/http_header_utils_test.dart create mode 100644 packages/dart/test/utils/http_sanitizer_test.dart create mode 100644 packages/dart/test/utils/regex_utils_test.dart create mode 100644 packages/dart/test/utils/sample_rate_format_test.dart create mode 100644 packages/dart/test/utils/tracing_utils_test.dart create mode 100644 packages/dart/test/utils/url_details_test.dart create mode 100644 packages/dart/test/utils_test.dart create mode 100644 packages/dart/test/version_test.dart create mode 100644 packages/dart/test_resources/envelope-no-attachment.envelope create mode 100644 packages/dart/test_resources/envelope-with-image.envelope create mode 100644 packages/dart/test_resources/sentry.png create mode 100644 packages/dart/test_resources/testfile.txt create mode 100755 packages/dart/tool/presubmit.sh diff --git a/.gitignore b/.gitignore index 9f397df184..df8f6f05dd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ .packages .dart_tool/ build/ -packages pubspec.lock .pub/ .idea/libraries diff --git a/packages/dart/AUTHORS b/packages/dart/AUTHORS new file mode 100644 index 0000000000..1739b1535d --- /dev/null +++ b/packages/dart/AUTHORS @@ -0,0 +1,8 @@ +# Below is a list of people and organizations that have contributed +# to package:sentry. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. +Simon Lightfoot +Hadrien Lejard diff --git a/packages/dart/CHANGELOG.md b/packages/dart/CHANGELOG.md new file mode 120000 index 0000000000..04c99a55ca --- /dev/null +++ b/packages/dart/CHANGELOG.md @@ -0,0 +1 @@ +../CHANGELOG.md \ No newline at end of file diff --git a/packages/dart/LICENSE b/packages/dart/LICENSE new file mode 100644 index 0000000000..6f2d1444dd --- /dev/null +++ b/packages/dart/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/packages/dart/PATENTS b/packages/dart/PATENTS new file mode 100644 index 0000000000..ac39faf679 --- /dev/null +++ b/packages/dart/PATENTS @@ -0,0 +1,17 @@ +Google hereby grants to you a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this +section) patent license to make, have made, use, offer to sell, sell, +import, transfer, and otherwise run, modify and propagate the contents +of this implementation, where such license applies only to those +patent claims, both currently owned by Google and acquired in the +future, licensable by Google that are necessarily infringed by this +implementation. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute +or order or agree to the institution of patent litigation or any other +patent enforcement activity against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that this +implementation constitutes direct or contributory patent infringement, +or inducement of patent infringement, then any patent rights granted +to you under this License for this implementation shall terminate as +of the date such litigation is filed. \ No newline at end of file diff --git a/packages/dart/README.md b/packages/dart/README.md new file mode 100644 index 0000000000..94e834e9b6 --- /dev/null +++ b/packages/dart/README.md @@ -0,0 +1,194 @@ +

+ + + +
+

+ +Sentry SDK for Dart +=========== + +| package | build | pub | likes | popularity | pub points | +| ------- | ------- | ------- | ------- | ------- | ------- | +| sentry | [![build](https://github.com/getsentry/sentry-dart/workflows/sentry-dart/badge.svg?branch=main)](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Asentry-dart) | [![pub package](https://img.shields.io/pub/v/sentry.svg)](https://pub.dev/packages/sentry) | [![likes](https://img.shields.io/pub/likes/sentry)](https://pub.dev/packages/sentry/score) | [![popularity](https://img.shields.io/pub/popularity/sentry)](https://img.shields.io/pub/sentry) | [![pub points](https://img.shields.io/pub/points/sentry)](https://pub.dev/packages/sentry/score) + +Pure Dart SDK used by any Dart application like AngularDart, CLI and Server. + +#### Flutter + +For Flutter applications there's [`sentry_flutter`](https://pub.dev/packages/sentry_flutter) which builds on top of this package. +That will give you native crash support (for Android and iOS), [release health](https://docs.sentry.io/product/releases/health/), offline caching and more. + +#### Usage + +- Sign up for a Sentry.io account and get a DSN at https://sentry.io. + +- Follow the installing instructions on [pub.dev](https://pub.dev/packages/sentry/install). + +- Initialize the Sentry SDK using the DSN issued by Sentry.io: + +```dart +import 'package:sentry/sentry.dart'; + +Future main() async { + await Sentry.init( + (options) { + options.dsn = 'https://example@sentry.io/example'; + }, + appRunner: initApp, // Init your App. + ); +} + +void initApp() { + // your app code +} +``` + +Or, if you want to run your app in your own error zone [runZonedGuarded]: + +```dart +import 'dart:async'; + +import 'package:sentry/sentry.dart'; + +Future main() async { + runZonedGuarded(() async { + await Sentry.init( + (options) { + options.dsn = 'https://example@sentry.io/example'; + }, + ); + + // Init your App. + initApp(); + }, (exception, stackTrace) async { + await Sentry.captureException(exception, stackTrace: stackTrace); + }); +} + +void initApp() { + // your app code +} +``` + +##### Breadcrumbs for HTTP Requests + +The `SentryHttpClient` can be used as a standalone client like this: +```dart +import 'package:sentry/sentry.dart'; + +var client = SentryHttpClient(); +try { + var uriResponse = await client.post('https://example.com/whatsit/create', + body: {'name': 'doodle', 'color': 'blue'}); + print(await client.get(uriResponse.bodyFields['uri'])); +} finally { + client.close(); +} +``` + +The `SentryHttpClient` can also be used as a wrapper for your own +HTTP [Client](https://pub.dev/documentation/http/latest/http/Client-class.html): +```dart +import 'package:sentry/sentry.dart'; +import 'package:http/http.dart' as http; + +final myClient = http.Client(); + +var client = SentryHttpClient(client: myClient); +try { +var uriResponse = await client.post('https://example.com/whatsit/create', + body: {'name': 'doodle', 'color': 'blue'}); + print(await client.get(uriResponse.bodyFields['uri'])); +} finally { + client.close(); +} +``` + +##### Reporting Bad HTTP Requests as Errors + +The `SentryHttpClient` can also catch exceptions that may occur during requests +such as [`SocketException`](https://api.dart.dev/stable/2.13.4/dart-io/SocketException-class.html)s. +This is currently an opt-in feature. The following example shows how to enable it. + +```dart +import 'package:sentry/sentry.dart'; + +var client = SentryHttpClient(); +try { +var uriResponse = await client.post('https://example.com/whatsit/create', + body: {'name': 'doodle', 'color': 'blue'}); + print(await client.get(uriResponse.bodyFields['uri'])); +} finally { + client.close(); +} +``` + +Furthermore you can track HTTP requests which are considered bad by you. +The following example shows how to do it. It captures exceptions for +each request with a status code range from 400 to 404 and also for 500. + +```dart +import 'package:sentry/sentry.dart'; + +var client = SentryHttpClient( + failedRequestStatusCodes: [ + SentryStatusCode.range(400, 404), + SentryStatusCode(500), + ], +); + +try { +var uriResponse = await client.post('https://example.com/whatsit/create', + body: {'name': 'doodle', 'color': 'blue'}); + print(await client.get(uriResponse.bodyFields['uri'])); +} finally { + client.close(); +} +``` + +##### Performance Monitoring for HTTP Requests + +The `SentryHttpClient` starts a span out of the active span bound to the scope for each HTTP Request. This is currently an opt-in feature. The following example shows how to enable it. + +```dart +import 'package:sentry/sentry.dart'; + +final transaction = Sentry.startTransaction( + 'webrequest', + 'request', + bindToScope: true, +); + +var client = SentryHttpClient(); +try { +var uriResponse = await client.post('https://example.com/whatsit/create', + body: {'name': 'doodle', 'color': 'blue'}); + print(await client.get(uriResponse.bodyFields['uri'])); +} finally { + client.close(); +} + +await transaction.finish(status: SpanStatus.ok()); +``` + +Read more about [Automatic Instrumentation](https://docs.sentry.io/platforms/dart/performance/instrumentation/automatic-instrumentation/). + +##### Tips for catching errors + +- Use a `try/catch` block. +- Use a `catchError` block for `Futures`, examples on [dart.dev](https://dart.dev/guides/libraries/futures-error-handling). +- The SDK already runs your `callback` on an error handler, e.g. using [runZonedGuarded](https://api.flutter.dev/flutter/dart-async/runZonedGuarded.html), events caught by the `runZonedGuarded` are captured automatically. +- [Current Isolate errors](https://api.flutter.dev/flutter/dart-isolate/Isolate/addErrorListener.html) which is the equivalent of a main or UI thread, are captured automatically (Only for non-Web Apps). +- For your own `Isolates`, add an [Error Listener](https://api.flutter.dev/flutter/dart-isolate/Isolate/addErrorListener.html) by calling `isolate.addSentryErrorListener()`. + +#### Resources + +#### Resources + +* [![Flutter docs](https://img.shields.io/badge/documentation-sentry.io-green.svg?label=flutter%20docs)](https://docs.sentry.io/platforms/flutter/) +* [![Dart docs](https://img.shields.io/badge/documentation-sentry.io-green.svg?label=dart%20docs)](https://docs.sentry.io/platforms/dart/) +* [![Discussions](https://img.shields.io/github/discussions/getsentry/sentry-dart.svg)](https://github.com/getsentry/sentry-dart/discussions) +* [![Discord Chat](https://img.shields.io/discord/621778831602221064?logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/gB6ja9uZuN) +* [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](https://stackoverflow.com/questions/tagged/sentry) +* [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](https://twitter.com/intent/follow?screen_name=getsentry) diff --git a/packages/dart/analysis_options.yaml b/packages/dart/analysis_options.yaml new file mode 100644 index 0000000000..74ed00b114 --- /dev/null +++ b/packages/dart/analysis_options.yaml @@ -0,0 +1,25 @@ +include: package:lints/recommended.yaml + +analyzer: + exclude: + - example/** # the example has its own 'analysis_options.yaml' + - test/*.mocks.dart + errors: + # treat missing required parameters as a warning (not a hint) + missing_required_param: error + # treat missing returns as a warning (not a hint) + missing_return: error + # allow having TODOs in the code + todo: ignore + # allow self-reference to deprecated members (we do this because otherwise we have + # to annotate every member in every test, assert, etc, when we deprecate something) + deprecated_member_use_from_same_package: warning + +linter: + rules: + prefer_relative_imports: true + unnecessary_brace_in_string_interps: true + prefer_function_declarations_over_variables: false + no_leading_underscores_for_local_identifiers: false + avoid_renaming_method_parameters: false + unawaited_futures: true diff --git a/packages/dart/class-diagram.svg b/packages/dart/class-diagram.svg new file mode 100644 index 0000000000..db98a9ad63 --- /dev/null +++ b/packages/dart/class-diagram.svg @@ -0,0 +1,3364 @@ + + + + + + + + +cluster~ + +dart + + +cluster~/lib + +lib + + +cluster~/lib/src + +src + + +cluster~/lib/src/environment + +environment + + +cluster~/lib/src/protocol + +protocol + + +cluster~/lib/src/event_processor + +event_processor + + +cluster~/lib/src/event_processor/exception + +exception + + +cluster~/lib/src/event_processor/enricher + +enricher + + +cluster~/lib/src/platform + +platform + + +cluster~/lib/src/sentry_attachment + +sentry_attachment + + +cluster~/lib/src/http_client + +http_client + + +cluster~/lib/src/transport + +transport + + +cluster~/lib/src/utils + +utils + + +cluster~/lib/src/client_reports + +client_reports + + + +/lib/src/noop_hub.dart + +noop_hub + + + +/lib/src/sentry_client.dart + +sentry_client + + + +/lib/src/noop_hub.dart->/lib/src/sentry_client.dart + + + + + +/lib/src/sentry_options.dart + +sentry_options + + + +/lib/src/noop_hub.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/hint.dart + +hint + + + +/lib/src/noop_hub.dart->/lib/src/hint.dart + + + + + +/lib/src/tracing.dart + +tracing + + + +/lib/src/noop_hub.dart->/lib/src/tracing.dart + + + + + +/lib/src/protocol.dart + +protocol + + + +/lib/src/noop_hub.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_user_feedback.dart + +sentry_user_feedback + + + +/lib/src/noop_hub.dart->/lib/src/sentry_user_feedback.dart + + + + + +/lib/src/scope.dart + +scope + + + +/lib/src/noop_hub.dart->/lib/src/scope.dart + + + + + +/lib/src/hub.dart + +hub + + + +/lib/src/noop_hub.dart->/lib/src/hub.dart + + + + + +/lib/src/sentry_event_like.dart + +sentry_event_like + + + +/lib/src/sentry_event_like.dart->/lib/src/protocol.dart + + + + + +/lib/src/version.dart + +version + + + +/lib/src/utils.dart + +utils + + + +/lib/src/sentry_span_context.dart + +sentry_span_context + + + +/lib/sentry.dart + +sentry + + + +/lib/src/sentry_span_context.dart->/lib/sentry.dart + + + + + +/lib/src/environment/_web_environment_variables.dart + +_web_environment_variables + + + +/lib/src/environment/keys.dart + +keys + + + +/lib/src/environment/_web_environment_variables.dart->/lib/src/environment/keys.dart + + + + + +/lib/src/environment/environment_variables.dart + +environment_variables + + + +/lib/src/environment/_web_environment_variables.dart->/lib/src/environment/environment_variables.dart + + + + + +/lib/src/environment/_io_environment_variables.dart + +_io_environment_variables + + + +/lib/src/environment/_io_environment_variables.dart->/lib/src/environment/_web_environment_variables.dart + + + + + +/lib/src/environment/_io_environment_variables.dart->/lib/src/environment/keys.dart + + + + + +/lib/src/environment/_io_environment_variables.dart->/lib/src/environment/environment_variables.dart + + + + + +/lib/src/platform_checker.dart + +platform_checker + + + +/lib/src/environment/environment_variables.dart->/lib/src/platform_checker.dart + + + + + +/lib/src/propagation_context.dart + +propagation_context + + + +/lib/src/sentry_baggage.dart + +sentry_baggage + + + +/lib/src/propagation_context.dart->/lib/src/sentry_baggage.dart + + + + + +/lib/src/propagation_context.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_trace_origins.dart + +sentry_trace_origins + + + +/lib/src/sentry_isolate_extension.dart + +sentry_isolate_extension + + + +/lib/src/hub_adapter.dart + +hub_adapter + + + +/lib/src/sentry_isolate_extension.dart->/lib/src/hub_adapter.dart + + + + + +/lib/src/sentry_isolate.dart + +sentry_isolate + + + +/lib/src/sentry_isolate_extension.dart->/lib/src/sentry_isolate.dart + + + + + +/lib/src/sentry_isolate_extension.dart->/lib/src/hub.dart + + + + + +/lib/src/sentry_item_type.dart + +sentry_item_type + + + +/lib/src/diagnostic_logger.dart + +diagnostic_logger + + + +/lib/src/diagnostic_logger.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/diagnostic_logger.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_traces_sampler.dart + +sentry_traces_sampler + + + +/lib/src/sentry_traces_sampler.dart->/lib/sentry.dart + + + + + +/lib/src/invalid_sentry_trace_header_exception.dart + +invalid_sentry_trace_header_exception + + + +/lib/src/sentry.dart + +sentry + + + +/lib/src/sentry.dart->/lib/src/noop_hub.dart + + + + + +/lib/src/sentry.dart->/lib/src/environment/environment_variables.dart + + + + + +/lib/src/sentry.dart->/lib/src/sentry_client.dart + + + + + +/lib/src/sentry.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/event_processor/deduplication_event_processor.dart + +deduplication_event_processor + + + +/lib/src/sentry.dart->/lib/src/event_processor/deduplication_event_processor.dart + + + + + +/lib/src/event_processor/exception/exception_event_processor.dart + +exception_event_processor + + + +/lib/src/sentry.dart->/lib/src/event_processor/exception/exception_event_processor.dart + + + + + +/lib/src/event_processor/enricher/enricher_event_processor.dart + +enricher_event_processor + + + +/lib/src/sentry.dart->/lib/src/event_processor/enricher/enricher_event_processor.dart + + + + + +/lib/src/sentry.dart->/lib/src/hint.dart + + + + + +/lib/src/sentry.dart->/lib/src/tracing.dart + + + + + +/lib/src/integration.dart + +integration + + + +/lib/src/sentry.dart->/lib/src/integration.dart + + + + + +/lib/src/sentry.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry.dart->/lib/src/hub_adapter.dart + + + + + +/lib/src/sentry.dart->/lib/src/sentry_user_feedback.dart + + + + + +/lib/src/run_zoned_guarded_integration.dart + +run_zoned_guarded_integration + + + +/lib/src/sentry.dart->/lib/src/run_zoned_guarded_integration.dart + + + + + +/lib/src/sentry.dart->/lib/src/hub.dart + + + + + +/lib/src/sentry_envelope_item.dart + +sentry_envelope_item + + + +/lib/src/sentry_envelope_item.dart->/lib/src/utils.dart + + + + + +/lib/src/sentry_envelope_item.dart->/lib/src/sentry_item_type.dart + + + + + +/lib/src/sentry_envelope_item_header.dart + +sentry_envelope_item_header + + + +/lib/src/sentry_envelope_item.dart->/lib/src/sentry_envelope_item_header.dart + + + + + +/lib/src/sentry_envelope_item.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_attachment/sentry_attachment.dart + +sentry_attachment + + + +/lib/src/sentry_envelope_item.dart->/lib/src/sentry_attachment/sentry_attachment.dart + + + + + +/lib/src/sentry_envelope_item.dart->/lib/src/sentry_user_feedback.dart + + + + + +/lib/src/client_reports/client_report.dart + +client_report + + + +/lib/src/sentry_envelope_item.dart->/lib/src/client_reports/client_report.dart + + + + + +/lib/src/sentry_client.dart->/lib/src/version.dart + + + + + +/lib/src/sentry_client.dart->/lib/src/sentry_baggage.dart + + + + + +/lib/src/sentry_client.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/sentry_client.dart->/lib/src/hint.dart + + + + + +/lib/src/sentry_trace_context_header.dart + +sentry_trace_context_header + + + +/lib/src/sentry_client.dart->/lib/src/sentry_trace_context_header.dart + + + + + +/lib/src/sentry_client.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_client.dart->/lib/src/sentry_attachment/sentry_attachment.dart + + + + + +/lib/src/sentry_client.dart->/lib/src/sentry_user_feedback.dart + + + + + +/lib/src/transport/noop_transport.dart + +noop_transport + + + +/lib/src/sentry_client.dart->/lib/src/transport/noop_transport.dart + + + + + +/lib/src/transport/data_category.dart + +data_category + + + +/lib/src/sentry_client.dart->/lib/src/transport/data_category.dart + + + + + +/lib/src/transport/rate_limiter.dart + +rate_limiter + + + +/lib/src/sentry_client.dart->/lib/src/transport/rate_limiter.dart + + + + + +/lib/src/transport/http_transport.dart + +http_transport + + + +/lib/src/sentry_client.dart->/lib/src/transport/http_transport.dart + + + + + +/lib/src/sentry_client.dart->/lib/src/scope.dart + + + + + +/lib/src/event_processor.dart + +event_processor + + + +/lib/src/sentry_client.dart->/lib/src/event_processor.dart + + + + + +/lib/src/utils/isolate_utils.dart + +isolate_utils + + + +/lib/src/sentry_client.dart->/lib/src/utils/isolate_utils.dart + + + + + +/lib/src/client_reports/client_report_recorder.dart + +client_report_recorder + + + +/lib/src/sentry_client.dart->/lib/src/client_reports/client_report_recorder.dart + + + + + +/lib/src/client_reports/discard_reason.dart + +discard_reason + + + +/lib/src/sentry_client.dart->/lib/src/client_reports/discard_reason.dart + + + + + +/lib/src/sentry_stack_trace_factory.dart + +sentry_stack_trace_factory + + + +/lib/src/sentry_client.dart->/lib/src/sentry_stack_trace_factory.dart + + + + + +/lib/src/sentry_envelope.dart + +sentry_envelope + + + +/lib/src/sentry_client.dart->/lib/src/sentry_envelope.dart + + + + + +/lib/src/sentry_exception_factory.dart + +sentry_exception_factory + + + +/lib/src/sentry_client.dart->/lib/src/sentry_exception_factory.dart + + + + + +/lib/src/protocol/span_id.dart + +span_id + + + +/lib/src/protocol/sentry_response.dart + +sentry_response + + + +/lib/src/protocol/contexts.dart + +contexts + + + +/lib/src/protocol/sentry_response.dart->/lib/src/protocol/contexts.dart + + + + + +/lib/src/utils/iterable_extension.dart + +iterable_extension + + + +/lib/src/protocol/sentry_response.dart->/lib/src/utils/iterable_extension.dart + + + + + +/lib/src/protocol/sentry_thread.dart + +sentry_thread + + + +/lib/src/protocol/sentry_stack_trace.dart + +sentry_stack_trace + + + +/lib/src/protocol/sentry_thread.dart->/lib/src/protocol/sentry_stack_trace.dart + + + + + +/lib/src/protocol/sentry_stack_frame.dart + +sentry_stack_frame + + + +/lib/src/protocol/sentry_transaction_info.dart + +sentry_transaction_info + + + +/lib/src/protocol/sentry_span.dart + +sentry_span + + + +/lib/src/protocol/sentry_span.dart->/lib/src/utils.dart + + + + + +/lib/src/protocol/sentry_span.dart->/lib/src/tracing.dart + + + + + +/lib/src/protocol/sentry_span.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_tracer.dart + +sentry_tracer + + + +/lib/src/protocol/sentry_span.dart->/lib/src/sentry_tracer.dart + + + + + +/lib/src/protocol/sentry_span.dart->/lib/src/hub.dart + + + + + +/lib/src/protocol/breadcrumb.dart + +breadcrumb + + + +/lib/src/protocol/breadcrumb.dart->/lib/src/utils.dart + + + + + +/lib/src/protocol/breadcrumb.dart->/lib/src/protocol.dart + + + + + +/lib/src/protocol/sentry_request.dart + +sentry_request + + + +/lib/src/utils/http_sanitizer.dart + +http_sanitizer + + + +/lib/src/protocol/sentry_request.dart->/lib/src/utils/http_sanitizer.dart + + + + + +/lib/src/protocol/sentry_request.dart->/lib/src/utils/iterable_extension.dart + + + + + +/lib/src/protocol/sentry_level.dart + +sentry_level + + + +/lib/src/protocol/sdk_version.dart + +sdk_version + + + +/lib/src/protocol/sentry_package.dart + +sentry_package + + + +/lib/src/protocol/sdk_version.dart->/lib/src/protocol/sentry_package.dart + + + + + +/lib/src/protocol/sentry_trace_header.dart + +sentry_trace_header + + + +/lib/src/protocol/sentry_trace_header.dart->/lib/src/invalid_sentry_trace_header_exception.dart + + + + + +/lib/src/protocol/sentry_trace_header.dart->/lib/src/protocol.dart + + + + + +/lib/src/protocol/sentry_transaction.dart + +sentry_transaction + + + +/lib/src/protocol/sentry_transaction.dart->/lib/src/utils.dart + + + + + +/lib/src/protocol/sentry_transaction.dart->/lib/src/protocol.dart + + + + + +/lib/src/protocol/sentry_transaction.dart->/lib/src/sentry_tracer.dart + + + + + +/lib/src/sentry_measurement.dart + +sentry_measurement + + + +/lib/src/protocol/sentry_transaction.dart->/lib/src/sentry_measurement.dart + + + + + +/lib/src/protocol/sentry_id.dart + +sentry_id + + + +/lib/src/protocol/sentry_view_hierarchy_element.dart + +sentry_view_hierarchy_element + + + +/lib/src/protocol/sentry_culture.dart + +sentry_culture + + + +/lib/src/protocol/sentry_geo.dart + +sentry_geo + + + +/lib/src/protocol/debug_meta.dart + +debug_meta + + + +/lib/src/protocol/debug_meta.dart->/lib/src/protocol.dart + + + + + +/lib/src/protocol/mechanism.dart + +mechanism + + + +/lib/src/protocol/sentry_app.dart + +sentry_app + + + +/lib/src/protocol/sentry_gpu.dart + +sentry_gpu + + + +/lib/src/protocol/sentry_trace_context.dart + +sentry_trace_context + + + +/lib/src/protocol/sentry_trace_context.dart->/lib/src/propagation_context.dart + + + + + +/lib/src/protocol/sentry_trace_context.dart->/lib/src/protocol.dart + + + + + +/lib/src/protocol/sentry_trace_context.dart->/lib/sentry.dart + + + + + +/lib/src/protocol/contexts.dart->/lib/src/protocol.dart + + + + + +/lib/src/protocol/debug_image.dart + +debug_image + + + +/lib/src/protocol/sentry_baggage_header.dart + +sentry_baggage_header + + + +/lib/src/protocol/sentry_baggage_header.dart->/lib/src/sentry_baggage.dart + + + + + +/lib/src/protocol/sentry_stack_trace.dart->/lib/src/protocol/sentry_stack_frame.dart + + + + + +/lib/src/protocol/sentry_runtime.dart + +sentry_runtime + + + +/lib/src/protocol/sentry_device.dart + +sentry_device + + + +/lib/src/protocol/sentry_device.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/protocol/sentry_message.dart + +sentry_message + + + +/lib/src/protocol/sdk_info.dart + +sdk_info + + + +/lib/src/protocol/max_body_size.dart + +max_body_size + + + +/lib/src/protocol/sentry_event.dart + +sentry_event + + + +/lib/src/protocol/sentry_event.dart->/lib/src/utils.dart + + + + + +/lib/src/throwable_mechanism.dart + +throwable_mechanism + + + +/lib/src/protocol/sentry_event.dart->/lib/src/throwable_mechanism.dart + + + + + +/lib/src/protocol/sentry_event.dart->/lib/src/protocol.dart + + + + + +/lib/src/protocol/sentry_operating_system.dart + +sentry_operating_system + + + +/lib/src/protocol/sentry_user.dart + +sentry_user + + + +/lib/src/protocol/sentry_user.dart->/lib/sentry.dart + + + + + +/lib/src/protocol/sentry_exception.dart + +sentry_exception + + + +/lib/src/protocol/sentry_exception.dart->/lib/src/protocol.dart + + + + + +/lib/src/protocol/span_status.dart + +span_status + + + +/lib/src/protocol/sentry_transaction_name_source.dart + +sentry_transaction_name_source + + + +/lib/src/protocol/sentry_view_hierarchy.dart + +sentry_view_hierarchy + + + +/lib/src/protocol/sentry_view_hierarchy.dart->/lib/src/protocol/sentry_view_hierarchy_element.dart + + + + + +/lib/src/protocol/sentry_browser.dart + +sentry_browser + + + +/lib/src/protocol/dsn.dart + +dsn + + + +/lib/src/sentry_baggage.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/sentry_baggage.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_baggage.dart->/lib/src/scope.dart + + + + + +/lib/src/exception_stacktrace_extractor.dart + +exception_stacktrace_extractor + + + +/lib/src/exception_stacktrace_extractor.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/exception_stacktrace_extractor.dart->/lib/src/protocol.dart + + + + + +/lib/src/isolate_error_integration.dart + +isolate_error_integration + + + +/lib/src/isolate_error_integration.dart->/lib/src/sentry_isolate_extension.dart + + + + + +/lib/src/isolate_error_integration.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/isolate_error_integration.dart->/lib/src/integration.dart + + + + + +/lib/src/isolate_error_integration.dart->/lib/src/hub.dart + + + + + +/lib/src/throwable_mechanism.dart->/lib/src/protocol/mechanism.dart + + + + + +/lib/src/type_check_hint.dart + +type_check_hint + + + +/lib/src/http_client/failed_request_client.dart + +failed_request_client + + + +/lib/src/type_check_hint.dart->/lib/src/http_client/failed_request_client.dart + + + + + +/lib/src/sentry_client_stub.dart + +sentry_client_stub + + + +/lib/src/sentry_client_stub.dart->/lib/src/sentry_client.dart + + + + + +/lib/src/sentry_client_stub.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/sentry_options.dart->/lib/src/version.dart + + + + + +/lib/src/sentry_options.dart->/lib/src/environment/environment_variables.dart + + + + + +/lib/src/sentry_options.dart->/lib/src/diagnostic_logger.dart + + + + + +/lib/src/noop_client.dart + +noop_client + + + +/lib/src/sentry_options.dart->/lib/src/noop_client.dart + + + + + +/lib/src/sentry_options.dart->/lib/src/transport/noop_transport.dart + + + + + +/lib/src/client_reports/noop_client_report_recorder.dart + +noop_client_report_recorder + + + +/lib/src/sentry_options.dart->/lib/src/client_reports/noop_client_report_recorder.dart + + + + + +/lib/src/sentry_options.dart->/lib/src/client_reports/client_report_recorder.dart + + + + + +/lib/src/sentry_options.dart->/lib/src/sentry_stack_trace_factory.dart + + + + + +/lib/src/sentry_options.dart->/lib/src/sentry_exception_factory.dart + + + + + +/lib/src/sentry_options.dart->/lib/sentry.dart + + + + + +/lib/src/event_processor/deduplication_event_processor.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/event_processor/deduplication_event_processor.dart->/lib/src/hint.dart + + + + + +/lib/src/event_processor/deduplication_event_processor.dart->/lib/src/protocol.dart + + + + + +/lib/src/event_processor/deduplication_event_processor.dart->/lib/src/event_processor.dart + + + + + +/lib/src/event_processor/exception/exception_event_processor.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/event_processor/exception/exception_event_processor.dart->/lib/src/event_processor.dart + + + + + +/lib/src/event_processor/exception/web_exception_event_processor.dart + +web_exception_event_processor + + + +/lib/src/event_processor/exception/web_exception_event_processor.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/event_processor/exception/web_exception_event_processor.dart->/lib/src/event_processor/exception/exception_event_processor.dart + + + + + +/lib/src/event_processor/exception/web_exception_event_processor.dart->/lib/src/hint.dart + + + + + +/lib/src/event_processor/exception/web_exception_event_processor.dart->/lib/src/protocol.dart + + + + + +/lib/src/event_processor/exception/io_exception_event_processor.dart + +io_exception_event_processor + + + +/lib/src/event_processor/exception/io_exception_event_processor.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/event_processor/exception/io_exception_event_processor.dart->/lib/src/event_processor/exception/exception_event_processor.dart + + + + + +/lib/src/event_processor/exception/io_exception_event_processor.dart->/lib/src/hint.dart + + + + + +/lib/src/event_processor/exception/io_exception_event_processor.dart->/lib/src/protocol.dart + + + + + +/lib/src/event_processor/enricher/io_enricher_event_processor.dart + +io_enricher_event_processor + + + +/lib/src/event_processor/enricher/io_enricher_event_processor.dart->/lib/src/event_processor/enricher/enricher_event_processor.dart + + + + + +/lib/src/event_processor/enricher/io_enricher_event_processor.dart->/lib/sentry.dart + + + + + +/lib/src/event_processor/enricher/enricher_event_processor.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/event_processor/enricher/enricher_event_processor.dart->/lib/src/event_processor.dart + + + + + +/lib/src/event_processor/enricher/web_enricher_event_processor.dart + +web_enricher_event_processor + + + +/lib/src/event_processor/enricher/web_enricher_event_processor.dart->/lib/src/event_processor/enricher/enricher_event_processor.dart + + + + + +/lib/src/event_processor/enricher/web_enricher_event_processor.dart->/lib/sentry.dart + + + + + +/lib/src/platform/platform.dart + +platform + + + +/lib/src/platform/_web_platform.dart + +_web_platform + + + +/lib/src/platform/_web_platform.dart->/lib/src/platform/platform.dart + + + + + +/lib/src/platform/_io_platform.dart + +_io_platform + + + +/lib/src/platform/_io_platform.dart->/lib/src/platform/platform.dart + + + + + +/lib/src/hint.dart->/lib/src/sentry_attachment/sentry_attachment.dart + + + + + +/lib/src/platform_checker.dart->/lib/src/platform/platform.dart + + + + + +/lib/src/tracing.dart->/lib/src/sentry_span_context.dart + + + + + +/lib/src/tracing.dart->/lib/src/invalid_sentry_trace_header_exception.dart + + + + + +/lib/src/tracing.dart->/lib/src/sentry_trace_context_header.dart + + + + + +/lib/src/sentry_measurement_unit.dart + +sentry_measurement_unit + + + +/lib/src/tracing.dart->/lib/src/sentry_measurement_unit.dart + + + + + +/lib/src/sentry_transaction_context.dart + +sentry_transaction_context + + + +/lib/src/tracing.dart->/lib/src/sentry_transaction_context.dart + + + + + +/lib/src/sentry_span_interface.dart + +sentry_span_interface + + + +/lib/src/tracing.dart->/lib/src/sentry_span_interface.dart + + + + + +/lib/src/sentry_traces_sampling_decision.dart + +sentry_traces_sampling_decision + + + +/lib/src/tracing.dart->/lib/src/sentry_traces_sampling_decision.dart + + + + + +/lib/src/noop_sentry_span.dart + +noop_sentry_span + + + +/lib/src/tracing.dart->/lib/src/noop_sentry_span.dart + + + + + +/lib/src/tracing.dart->/lib/src/sentry_measurement.dart + + + + + +/lib/src/sentry_sampling_context.dart + +sentry_sampling_context + + + +/lib/src/tracing.dart->/lib/src/sentry_sampling_context.dart + + + + + +/lib/src/exception_cause.dart + +exception_cause + + + +/lib/src/noop_isolate_error_integration.dart + +noop_isolate_error_integration + + + +/lib/src/noop_isolate_error_integration.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/noop_isolate_error_integration.dart->/lib/src/integration.dart + + + + + +/lib/src/noop_isolate_error_integration.dart->/lib/src/hub.dart + + + + + +/lib/src/sentry_trace_context_header.dart->/lib/src/protocol/sentry_id.dart + + + + + +/lib/src/sentry_trace_context_header.dart->/lib/src/sentry_baggage.dart + + + + + +/lib/src/sentry_trace_context_header.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/integration.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/integration.dart->/lib/src/hub.dart + + + + + +/lib/src/protocol.dart->/lib/src/sentry_event_like.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/span_id.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_response.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_thread.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_stack_frame.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_transaction_info.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_span.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/breadcrumb.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_request.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_level.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sdk_version.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_trace_header.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_transaction.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_id.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_view_hierarchy_element.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_culture.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_geo.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/debug_meta.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/mechanism.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_app.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_gpu.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_trace_context.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/contexts.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/debug_image.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_baggage_header.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_stack_trace.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_runtime.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_device.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_message.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sdk_info.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/max_body_size.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_package.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_event.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_operating_system.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_user.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_exception.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/span_status.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_transaction_name_source.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_view_hierarchy.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/sentry_browser.dart + + + + + +/lib/src/protocol.dart->/lib/src/protocol/dsn.dart + + + + + +/lib/src/sentry_attachment/io_sentry_attachment.dart + +io_sentry_attachment + + + +/lib/src/sentry_attachment/io_sentry_attachment.dart->/lib/src/sentry_attachment/sentry_attachment.dart + + + + + +/lib/src/sentry_attachment/sentry_attachment.dart->/lib/src/utils.dart + + + + + +/lib/src/sentry_attachment/sentry_attachment.dart->/lib/src/protocol/sentry_view_hierarchy.dart + + + + + +/lib/src/exception_cause_extractor.dart + +exception_cause_extractor + + + +/lib/src/exception_cause_extractor.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/exception_cause_extractor.dart->/lib/src/exception_cause.dart + + + + + +/lib/src/exception_cause_extractor.dart->/lib/src/protocol.dart + + + + + +/lib/src/noop_sentry_client.dart + +noop_sentry_client + + + +/lib/src/noop_sentry_client.dart->/lib/src/sentry_client.dart + + + + + +/lib/src/noop_sentry_client.dart->/lib/src/hint.dart + + + + + +/lib/src/noop_sentry_client.dart->/lib/src/sentry_trace_context_header.dart + + + + + +/lib/src/noop_sentry_client.dart->/lib/src/protocol.dart + + + + + +/lib/src/noop_sentry_client.dart->/lib/src/sentry_user_feedback.dart + + + + + +/lib/src/noop_sentry_client.dart->/lib/src/scope.dart + + + + + +/lib/src/noop_sentry_client.dart->/lib/src/sentry_envelope.dart + + + + + +/lib/src/sentry_transaction_context.dart->/lib/src/sentry_trace_origins.dart + + + + + +/lib/src/sentry_transaction_context.dart->/lib/src/sentry_baggage.dart + + + + + +/lib/src/sentry_transaction_context.dart->/lib/src/tracing.dart + + + + + +/lib/src/sentry_transaction_context.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_tracer_finish_status.dart + +sentry_tracer_finish_status + + + +/lib/src/sentry_tracer.dart->/lib/src/sentry_tracer_finish_status.dart + + + + + +/lib/src/utils/sample_rate_format.dart + +sample_rate_format + + + +/lib/src/sentry_tracer.dart->/lib/src/utils/sample_rate_format.dart + + + + + +/lib/src/sentry_tracer.dart->/lib/sentry.dart + + + + + +/lib/src/sentry_span_interface.dart->/lib/src/tracing.dart + + + + + +/lib/src/sentry_span_interface.dart->/lib/src/protocol.dart + + + + + +/lib/src/noop_origin.dart + +noop_origin + + + +/lib/src/hub_adapter.dart->/lib/src/sentry.dart + + + + + +/lib/src/hub_adapter.dart->/lib/src/sentry_client.dart + + + + + +/lib/src/hub_adapter.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/hub_adapter.dart->/lib/src/hint.dart + + + + + +/lib/src/hub_adapter.dart->/lib/src/tracing.dart + + + + + +/lib/src/hub_adapter.dart->/lib/src/protocol.dart + + + + + +/lib/src/hub_adapter.dart->/lib/src/sentry_user_feedback.dart + + + + + +/lib/src/hub_adapter.dart->/lib/src/scope.dart + + + + + +/lib/src/hub_adapter.dart->/lib/src/hub.dart + + + + + +/lib/src/sentry_user_feedback.dart->/lib/src/protocol.dart + + + + + +/lib/src/http_client/failed_request_client.dart->/lib/src/throwable_mechanism.dart + + + + + +/lib/src/http_client/failed_request_client.dart->/lib/src/type_check_hint.dart + + + + + +/lib/src/http_client/failed_request_client.dart->/lib/src/hint.dart + + + + + +/lib/src/http_client/failed_request_client.dart->/lib/src/protocol.dart + + + + + +/lib/src/http_client/failed_request_client.dart->/lib/src/hub_adapter.dart + + + + + +/lib/src/http_client/sentry_http_client.dart + +sentry_http_client + + + +/lib/src/http_client/failed_request_client.dart->/lib/src/http_client/sentry_http_client.dart + + + + + +/lib/src/http_client/sentry_http_client_error.dart + +sentry_http_client_error + + + +/lib/src/http_client/failed_request_client.dart->/lib/src/http_client/sentry_http_client_error.dart + + + + + +/lib/src/utils/tracing_utils.dart + +tracing_utils + + + +/lib/src/http_client/failed_request_client.dart->/lib/src/utils/tracing_utils.dart + + + + + +/lib/src/http_client/failed_request_client.dart->/lib/src/hub.dart + + + + + +/lib/src/http_client/sentry_http_client.dart->/lib/src/hub_adapter.dart + + + + + +/lib/src/http_client/sentry_http_client.dart->/lib/src/http_client/failed_request_client.dart + + + + + +/lib/src/http_client/breadcrumb_client.dart + +breadcrumb_client + + + +/lib/src/http_client/sentry_http_client.dart->/lib/src/http_client/breadcrumb_client.dart + + + + + +/lib/src/http_client/tracing_client.dart + +tracing_client + + + +/lib/src/http_client/sentry_http_client.dart->/lib/src/http_client/tracing_client.dart + + + + + +/lib/src/http_client/sentry_http_client.dart->/lib/src/hub.dart + + + + + +/lib/src/http_client/breadcrumb_client.dart->/lib/src/protocol.dart + + + + + +/lib/src/http_client/breadcrumb_client.dart->/lib/src/hub_adapter.dart + + + + + +/lib/src/http_client/breadcrumb_client.dart->/lib/src/utils/http_sanitizer.dart + + + + + +/lib/src/utils/url_details.dart + +url_details + + + +/lib/src/http_client/breadcrumb_client.dart->/lib/src/utils/url_details.dart + + + + + +/lib/src/http_client/breadcrumb_client.dart->/lib/src/hub.dart + + + + + +/lib/src/http_client/tracing_client.dart->/lib/src/sentry_trace_origins.dart + + + + + +/lib/src/http_client/tracing_client.dart->/lib/src/tracing.dart + + + + + +/lib/src/http_client/tracing_client.dart->/lib/src/protocol.dart + + + + + +/lib/src/http_client/tracing_client.dart->/lib/src/hub_adapter.dart + + + + + +/lib/src/http_client/tracing_client.dart->/lib/src/utils/http_sanitizer.dart + + + + + +/lib/src/http_client/tracing_client.dart->/lib/src/utils/tracing_utils.dart + + + + + +/lib/src/http_client/tracing_client.dart->/lib/src/hub.dart + + + + + +/lib/src/noop_sentry_span.dart->/lib/src/utils.dart + + + + + +/lib/src/noop_sentry_span.dart->/lib/src/tracing.dart + + + + + +/lib/src/noop_sentry_span.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_tracer_finish_status.dart->/lib/sentry.dart + + + + + +/lib/src/run_zoned_guarded_integration.dart->/lib/src/throwable_mechanism.dart + + + + + +/lib/src/run_zoned_guarded_integration.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/run_zoned_guarded_integration.dart->/lib/src/integration.dart + + + + + +/lib/src/run_zoned_guarded_integration.dart->/lib/src/protocol.dart + + + + + +/lib/src/run_zoned_guarded_integration.dart->/lib/src/hub.dart + + + + + +/lib/src/transport/noop_transport.dart->/lib/src/protocol.dart + + + + + +/lib/src/transport/transport.dart + +transport + + + +/lib/src/transport/noop_transport.dart->/lib/src/transport/transport.dart + + + + + +/lib/src/transport/noop_transport.dart->/lib/src/sentry_envelope.dart + + + + + +/lib/src/transport/rate_limit_parser.dart + +rate_limit_parser + + + +/lib/src/transport/rate_limit_parser.dart->/lib/src/transport/data_category.dart + + + + + +/lib/src/transport/rate_limit.dart + +rate_limit + + + +/lib/src/transport/rate_limit_parser.dart->/lib/src/transport/rate_limit.dart + + + + + +/lib/src/transport/noop_encode.dart + +noop_encode + + + +/lib/src/transport/transport.dart->/lib/src/protocol.dart + + + + + +/lib/src/transport/transport.dart->/lib/src/sentry_envelope.dart + + + + + +/lib/src/transport/rate_limiter.dart->/lib/src/sentry_envelope_item.dart + + + + + +/lib/src/transport/rate_limiter.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/transport/rate_limiter.dart->/lib/src/transport/rate_limit_parser.dart + + + + + +/lib/src/transport/rate_limiter.dart->/lib/src/transport/data_category.dart + + + + + +/lib/src/transport/rate_limiter.dart->/lib/src/transport/rate_limit.dart + + + + + +/lib/src/transport/rate_limiter.dart->/lib/src/client_reports/discard_reason.dart + + + + + +/lib/src/transport/rate_limiter.dart->/lib/src/sentry_envelope.dart + + + + + +/lib/src/transport/encode.dart + +encode + + + +/lib/src/transport/rate_limit.dart->/lib/src/transport/data_category.dart + + + + + +/lib/src/transport/http_transport.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/transport/http_transport.dart->/lib/src/noop_client.dart + + + + + +/lib/src/transport/http_transport.dart->/lib/src/protocol.dart + + + + + +/lib/src/transport/http_transport.dart->/lib/src/transport/noop_encode.dart + + + + + +/lib/src/transport/http_transport.dart->/lib/src/transport/data_category.dart + + + + + +/lib/src/transport/http_transport.dart->/lib/src/transport/transport.dart + + + + + +/lib/src/transport/http_transport.dart->/lib/src/transport/rate_limiter.dart + + + + + +/lib/src/transport/http_transport.dart->/lib/src/client_reports/client_report_recorder.dart + + + + + +/lib/src/transport/http_transport.dart->/lib/src/client_reports/discard_reason.dart + + + + + +/lib/src/transport/http_transport.dart->/lib/src/sentry_envelope.dart + + + + + +/lib/src/recursive_exception_cause_extractor.dart + +recursive_exception_cause_extractor + + + +/lib/src/recursive_exception_cause_extractor.dart->/lib/src/throwable_mechanism.dart + + + + + +/lib/src/recursive_exception_cause_extractor.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/recursive_exception_cause_extractor.dart->/lib/src/exception_cause.dart + + + + + +/lib/src/recursive_exception_cause_extractor.dart->/lib/src/protocol.dart + + + + + +/lib/src/scope.dart->/lib/src/propagation_context.dart + + + + + +/lib/src/scope.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/scope.dart->/lib/src/hint.dart + + + + + +/lib/src/scope.dart->/lib/src/protocol.dart + + + + + +/lib/src/scope.dart->/lib/src/sentry_attachment/sentry_attachment.dart + + + + + +/lib/src/scope.dart->/lib/src/sentry_tracer.dart + + + + + +/lib/src/scope.dart->/lib/src/sentry_span_interface.dart + + + + + +/lib/src/scope.dart->/lib/src/event_processor.dart + + + + + +/lib/src/scope_observer.dart + +scope_observer + + + +/lib/src/scope.dart->/lib/src/scope_observer.dart + + + + + +/lib/src/event_processor.dart->/lib/src/hint.dart + + + + + +/lib/src/event_processor.dart->/lib/src/protocol.dart + + + + + +/lib/src/utils/http_sanitizer.dart->/lib/src/protocol.dart + + + + + +/lib/src/utils/http_sanitizer.dart->/lib/src/utils/url_details.dart + + + + + +/lib/src/utils/url_details.dart->/lib/sentry.dart + + + + + +/lib/src/utils/http_header_utils.dart + +http_header_utils + + + +/lib/src/utils/_web_get_isolate_name.dart + +_web_get_isolate_name + + + +/lib/src/utils/tracing_utils.dart->/lib/sentry.dart + + + + + +/lib/src/utils/_io_get_isolate_name.dart + +_io_get_isolate_name + + + +/lib/src/client_reports/client_report.dart->/lib/src/utils.dart + + + + + +/lib/src/client_reports/discarded_event.dart + +discarded_event + + + +/lib/src/client_reports/client_report.dart->/lib/src/client_reports/discarded_event.dart + + + + + +/lib/src/client_reports/noop_client_report_recorder.dart->/lib/src/transport/data_category.dart + + + + + +/lib/src/client_reports/noop_client_report_recorder.dart->/lib/src/client_reports/client_report.dart + + + + + +/lib/src/client_reports/noop_client_report_recorder.dart->/lib/src/client_reports/client_report_recorder.dart + + + + + +/lib/src/client_reports/noop_client_report_recorder.dart->/lib/src/client_reports/discard_reason.dart + + + + + +/lib/src/client_reports/discarded_event.dart->/lib/src/transport/data_category.dart + + + + + +/lib/src/client_reports/discarded_event.dart->/lib/src/client_reports/discard_reason.dart + + + + + +/lib/src/client_reports/client_report_recorder.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/client_reports/client_report_recorder.dart->/lib/src/transport/data_category.dart + + + + + +/lib/src/client_reports/client_report_recorder.dart->/lib/src/client_reports/client_report.dart + + + + + +/lib/src/client_reports/client_report_recorder.dart->/lib/src/client_reports/discarded_event.dart + + + + + +/lib/src/client_reports/client_report_recorder.dart->/lib/src/client_reports/discard_reason.dart + + + + + +/lib/src/sentry_stack_trace_factory.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/sentry_stack_trace_factory.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_stack_trace_factory.dart->/lib/src/noop_origin.dart + + + + + +/lib/src/sentry_measurement.dart->/lib/src/sentry_measurement_unit.dart + + + + + +/lib/src/sentry_envelope_header.dart + +sentry_envelope_header + + + +/lib/src/sentry_envelope_header.dart->/lib/src/utils.dart + + + + + +/lib/src/sentry_envelope_header.dart->/lib/src/protocol/sdk_version.dart + + + + + +/lib/src/sentry_envelope_header.dart->/lib/src/protocol/sentry_id.dart + + + + + +/lib/src/sentry_envelope_header.dart->/lib/src/sentry_trace_context_header.dart + + + + + +/lib/src/sentry_isolate.dart->/lib/src/throwable_mechanism.dart + + + + + +/lib/src/sentry_isolate.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_isolate.dart->/lib/src/hub_adapter.dart + + + + + +/lib/src/sentry_isolate.dart->/lib/src/hub.dart + + + + + +/lib/src/scope_observer.dart->/lib/src/protocol/breadcrumb.dart + + + + + +/lib/src/scope_observer.dart->/lib/src/protocol/sentry_user.dart + + + + + +/lib/src/sentry_envelope.dart->/lib/src/utils.dart + + + + + +/lib/src/sentry_envelope.dart->/lib/src/sentry_item_type.dart + + + + + +/lib/src/sentry_envelope.dart->/lib/src/sentry_envelope_item.dart + + + + + +/lib/src/sentry_envelope.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/sentry_envelope.dart->/lib/src/sentry_trace_context_header.dart + + + + + +/lib/src/sentry_envelope.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_envelope.dart->/lib/src/sentry_attachment/sentry_attachment.dart + + + + + +/lib/src/sentry_envelope.dart->/lib/src/sentry_user_feedback.dart + + + + + +/lib/src/sentry_envelope.dart->/lib/src/client_reports/client_report.dart + + + + + +/lib/src/sentry_envelope.dart->/lib/src/sentry_envelope_header.dart + + + + + +/lib/src/sentry_sampling_context.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/sentry_sampling_context.dart->/lib/src/tracing.dart + + + + + +/lib/src/sentry_exception_factory.dart->/lib/src/throwable_mechanism.dart + + + + + +/lib/src/sentry_exception_factory.dart->/lib/src/sentry_options.dart + + + + + +/lib/src/sentry_exception_factory.dart->/lib/src/protocol.dart + + + + + +/lib/src/sentry_exception_factory.dart->/lib/src/recursive_exception_cause_extractor.dart + + + + + +/lib/src/sentry_exception_factory.dart->/lib/src/sentry_stack_trace_factory.dart + + + + + +/lib/src/hub.dart->/lib/src/propagation_context.dart + + + + + +/lib/src/hub.dart->/lib/src/sentry_traces_sampler.dart + + + + + +/lib/src/hub.dart->/lib/src/sentry_tracer.dart + + + + + +/lib/src/hub.dart->/lib/src/transport/data_category.dart + + + + + +/lib/src/hub.dart->/lib/src/client_reports/discard_reason.dart + + + + + +/lib/src/hub.dart->/lib/sentry.dart + + + + + +/lib/src/origin.dart + +origin + + + +/lib/sentry.dart->/lib/src/utils.dart + + + + + +/lib/sentry.dart->/lib/src/sentry_trace_origins.dart + + + + + +/lib/sentry.dart->/lib/src/sentry.dart + + + + + +/lib/sentry.dart->/lib/src/sentry_envelope_item.dart + + + + + +/lib/sentry.dart->/lib/src/sentry_client.dart + + + + + +/lib/sentry.dart->/lib/src/sentry_baggage.dart + + + + + +/lib/sentry.dart->/lib/src/exception_stacktrace_extractor.dart + + + + + +/lib/sentry.dart->/lib/src/throwable_mechanism.dart + + + + + +/lib/sentry.dart->/lib/src/type_check_hint.dart + + + + + +/lib/sentry.dart->/lib/src/sentry_options.dart + + + + + +/lib/sentry.dart->/lib/src/hint.dart + + + + + +/lib/sentry.dart->/lib/src/platform_checker.dart + + + + + +/lib/sentry.dart->/lib/src/tracing.dart + + + + + +/lib/sentry.dart->/lib/src/exception_cause.dart + + + + + +/lib/sentry.dart->/lib/src/integration.dart + + + + + +/lib/sentry.dart->/lib/src/protocol.dart + + + + + +/lib/sentry.dart->/lib/src/sentry_attachment/sentry_attachment.dart + + + + + +/lib/sentry.dart->/lib/src/exception_cause_extractor.dart + + + + + +/lib/sentry.dart->/lib/src/hub_adapter.dart + + + + + +/lib/sentry.dart->/lib/src/sentry_user_feedback.dart + + + + + +/lib/sentry.dart->/lib/src/http_client/sentry_http_client.dart + + + + + +/lib/sentry.dart->/lib/src/http_client/sentry_http_client_error.dart + + + + + +/lib/sentry.dart->/lib/src/run_zoned_guarded_integration.dart + + + + + +/lib/sentry.dart->/lib/src/transport/transport.dart + + + + + +/lib/sentry.dart->/lib/src/scope.dart + + + + + +/lib/sentry.dart->/lib/src/event_processor.dart + + + + + +/lib/sentry.dart->/lib/src/utils/http_sanitizer.dart + + + + + +/lib/sentry.dart->/lib/src/utils/url_details.dart + + + + + +/lib/sentry.dart->/lib/src/utils/http_header_utils.dart + + + + + +/lib/sentry.dart->/lib/src/utils/tracing_utils.dart + + + + + +/lib/sentry.dart->/lib/src/scope_observer.dart + + + + + +/lib/sentry.dart->/lib/src/sentry_envelope.dart + + + + + +/lib/sentry.dart->/lib/src/hub.dart + + + + + +/lib/sentry_io.dart + +sentry_io + + + +/lib/sentry_io.dart->/lib/src/sentry_isolate_extension.dart + + + + + +/lib/sentry_io.dart->/lib/src/sentry_attachment/io_sentry_attachment.dart + + + + + +/lib/sentry_io.dart->/lib/src/sentry_isolate.dart + + + + + +/lib/sentry_io.dart->/lib/sentry.dart + + + + + diff --git a/packages/dart/dartdoc_options.yaml b/packages/dart/dartdoc_options.yaml new file mode 100644 index 0000000000..20edf7f942 --- /dev/null +++ b/packages/dart/dartdoc_options.yaml @@ -0,0 +1,3 @@ +dartdoc: + errors: + - unresolved-doc-reference diff --git a/packages/dart/example/.gitignore b/packages/dart/example/.gitignore new file mode 100644 index 0000000000..3c8a157278 --- /dev/null +++ b/packages/dart/example/.gitignore @@ -0,0 +1,6 @@ +# Files and directories created by pub. +.dart_tool/ +.packages + +# Conventional directory for build output. +build/ diff --git a/packages/dart/example/README.md b/packages/dart/example/README.md new file mode 100644 index 0000000000..9e62e425d0 --- /dev/null +++ b/packages/dart/example/README.md @@ -0,0 +1,14 @@ +# `package:sentry` example + +The example in this directory throws an error and sends it to Sentry.io. Use it +as a source of example code, or to smoke-test your Sentry.io configuration. + +To use the example, create a Sentry.io account and get a DSN for your project. +In the `main.dart` file, replace the `dsn` value with the one you got from +Sentry.io. Then run the following command : + + +```sh +dart pub get +dart run +``` diff --git a/packages/dart/example/analysis_options.yaml b/packages/dart/example/analysis_options.yaml new file mode 100644 index 0000000000..f6968116a5 --- /dev/null +++ b/packages/dart/example/analysis_options.yaml @@ -0,0 +1,5 @@ +# Defines a default set of lint rules enforced for projects at Google. For +# details and rationale, see +# https://github.com/dart-lang/pedantic#enabled-lints. + +include: package:lints/recommended.yaml \ No newline at end of file diff --git a/packages/dart/example/bin/event_example.dart b/packages/dart/example/bin/event_example.dart new file mode 100644 index 0000000000..40d7932474 --- /dev/null +++ b/packages/dart/example/bin/event_example.dart @@ -0,0 +1,77 @@ +import 'package:sentry/src/protocol.dart'; + +final event = SentryEvent( + logger: 'main', + serverName: 'server.dart', + release: '1.4.0-preview.1', + environment: 'Test', + message: SentryMessage('This is an example Dart event.'), + tags: const {'project-id': '7371'}, + // ignore: deprecated_member_use + extra: const {'section': '1'}, + // fingerprint: const ['example-dart'], fingerprint forces events to group together + user: SentryUser( + id: '800', + username: 'first-user', + email: 'first@user.lan', + // ipAddress: '127.0.0.1', sendDefaultPii feature is enabled + // ignore: deprecated_member_use + extras: {'first-sign-in': '2020-01-01'}, + ), + breadcrumbs: [ + Breadcrumb( + message: 'UI Lifecycle', + timestamp: DateTime.now().toUtc(), + category: 'ui.lifecycle', + type: 'navigation', + data: {'screen': 'MainActivity', 'state': 'created'}, + level: SentryLevel.info, + ) + ], + contexts: Contexts( + operatingSystem: SentryOperatingSystem( + name: 'Android', + version: '5.0.2', + build: 'LRX22G.P900XXS0BPL2', + kernelVersion: + 'Linux version 3.4.39-5726670 (dpi@SWHC3807) (gcc version 4.8 (GCC) ) #1 SMP PREEMPT Thu Dec 1 19:42:39 KST 2016', + rooted: false, + ), + runtimes: [SentryRuntime(name: 'ART', version: '5')], + app: SentryApp( + name: 'Example Dart App', + version: '1.42.0', + identifier: 'HGT-App-13', + build: '93785', + buildType: 'release', + deviceAppHash: '5afd3a6', + startTime: DateTime.now().toUtc(), + ), + browser: SentryBrowser(name: 'Firefox', version: '42.0.1'), + device: SentryDevice( + name: 'SM-P900', + family: 'SM-P900', + model: 'SM-P900 (LRX22G)', + modelId: 'LRX22G', + arch: 'armeabi-v7a', + batteryLevel: 99, + orientation: SentryOrientation.landscape, + manufacturer: 'samsung', + brand: 'samsung', + screenDensity: 2.1, + screenDpi: 320, + online: true, + charging: true, + lowMemory: true, + simulator: false, + memorySize: 1500, + freeMemory: 200, + usableMemory: 4294967296, + storageSize: 4294967296, + freeStorage: 2147483648, + externalStorageSize: 8589934592, + externalFreeStorage: 2863311530, + bootTime: DateTime.now().toUtc(), + ), + ), +); diff --git a/packages/dart/example/bin/example.dart b/packages/dart/example/bin/example.dart new file mode 100644 index 0000000000..d7530f5874 --- /dev/null +++ b/packages/dart/example/bin/example.dart @@ -0,0 +1,110 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:sentry/sentry.dart'; + +import 'event_example.dart'; + +/// Sends a test exception report to Sentry.io using this Dart client. +Future main() async { + // ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io + const dsn = + 'https://e85b375ffb9f43cf8bdf9787768149e0@o447951.ingest.sentry.io/5428562'; + + await Sentry.init( + (options) => options + ..dsn = dsn + ..debug = true + ..sendDefaultPii = true + ..addEventProcessor(TagEventProcessor()), + appRunner: runApp, + ); +} + +Future runApp() async { + print('\nReporting a complete event example: '); + + Sentry.addBreadcrumb( + Breadcrumb( + message: 'Authenticated user', + category: 'auth', + type: 'debug', + data: { + 'admin': true, + 'permissions': [1, 2, 3] + }, + ), + ); + + await Sentry.configureScope((scope) async { + await scope.setUser(SentryUser( + id: '800', + username: 'first-user', + email: 'first@user.lan', + // ipAddress: '127.0.0.1', sendDefaultPii feature is enabled + // ignore: deprecated_member_use + extras: {'first-sign-in': '2020-01-01'}, + )); + scope + // ..fingerprint = ['example-dart'], fingerprint forces events to group together + ..transaction = '/example/app' + ..level = SentryLevel.warning; + await scope.setTag('build', '579'); + await scope.setExtra('company-name', 'Dart Inc'); + }); + + // Sends a full Sentry event payload to show the different parts of the UI. + final sentryId = await Sentry.captureEvent(event); + + print('Capture event result : SentryId : $sentryId'); + + print('\nCapture message: '); + + // Sends a full Sentry event payload to show the different parts of the UI. + final messageSentryId = await Sentry.captureMessage( + 'Message 1', + level: SentryLevel.warning, + template: 'Message %s', + params: ['1'], + ); + + print('Capture message result : SentryId : $messageSentryId'); + + try { + await loadConfig(); + } catch (error, stackTrace) { + print('\nReporting the following stack trace: '); + print(stackTrace); + final sentryId = await Sentry.captureException( + error, + stackTrace: stackTrace, + ); + + print('Capture exception result : SentryId : $sentryId'); + } + + // capture unhandled error + await loadConfig(); +} + +Future loadConfig() async { + await parseConfig(); +} + +Future parseConfig() async { + await decode(); +} + +Future decode() async { + throw StateError('This is a test error'); +} + +class TagEventProcessor implements EventProcessor { + @override + SentryEvent? apply(SentryEvent event, hint) { + return event..tags?.addAll({'page-locale': 'en-us'}); + } +} diff --git a/packages/dart/example/pubspec.yaml b/packages/dart/example/pubspec.yaml new file mode 100644 index 0000000000..4851622d44 --- /dev/null +++ b/packages/dart/example/pubspec.yaml @@ -0,0 +1,15 @@ +name: example +description: A simple command-line application. +version: 1.0.0 + +publish_to: none + +environment: + sdk: '>=3.5.0 <4.0.0' + +dependencies: + sentry: + path: ../../dart + +dev_dependencies: + lints: ^2.0.0 diff --git a/packages/dart/example/pubspec_overrides.yaml b/packages/dart/example/pubspec_overrides.yaml new file mode 100644 index 0000000000..fb3ef0716c --- /dev/null +++ b/packages/dart/example/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# melos_managed_dependency_overrides: sentry +dependency_overrides: + sentry: + path: .. diff --git a/packages/dart/example_web/.gitignore b/packages/dart/example_web/.gitignore new file mode 100644 index 0000000000..3d64647b50 --- /dev/null +++ b/packages/dart/example_web/.gitignore @@ -0,0 +1,9 @@ +# Files and directories created by pub +.dart_tool/ +.packages + +# Conventional directory for build outputs +build/ + +# Directory created by dartdoc +doc/api/ diff --git a/packages/dart/example_web/README.md b/packages/dart/example_web/README.md new file mode 100644 index 0000000000..98202566f6 --- /dev/null +++ b/packages/dart/example_web/README.md @@ -0,0 +1,8 @@ +# Sentry Dart : web example + +```sh +dart pub get + +# run the project ( see https://dart.dev/tools/webdev#serve ) +dart run webdev serve --release +``` diff --git a/packages/dart/example_web/analysis_options.yaml b/packages/dart/example_web/analysis_options.yaml new file mode 100644 index 0000000000..be16ace7d1 --- /dev/null +++ b/packages/dart/example_web/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:lints/recommended.yaml + +analyzer: + errors: + path_does_not_exist: ignore diff --git a/packages/dart/example_web/pubspec.yaml b/packages/dart/example_web/pubspec.yaml new file mode 100644 index 0000000000..5001acbef0 --- /dev/null +++ b/packages/dart/example_web/pubspec.yaml @@ -0,0 +1,18 @@ +name: sentry_dart_web_example +description: An absolute bare-bones web app. + +publish_to: 'none' + +environment: + sdk: '>=3.5.0 <4.0.0' + +dependencies: + sentry: + path: ../../dart/ + web: ^1.1.0 + +dev_dependencies: + build_runner: ^2.4.2 + build_web_compilers: ^4.0.3 + lints: ^2.0.0 + webdev: ^3.0.4 diff --git a/packages/dart/example_web/pubspec_overrides.yaml b/packages/dart/example_web/pubspec_overrides.yaml new file mode 100644 index 0000000000..fb3ef0716c --- /dev/null +++ b/packages/dart/example_web/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# melos_managed_dependency_overrides: sentry +dependency_overrides: + sentry: + path: .. diff --git a/packages/dart/example_web/web/event.dart b/packages/dart/example_web/web/event.dart new file mode 100644 index 0000000000..51f1be5cb7 --- /dev/null +++ b/packages/dart/example_web/web/event.dart @@ -0,0 +1,76 @@ +import 'package:sentry/src/protocol.dart'; + +final event = SentryEvent( + logger: 'main', + serverName: 'server.dart', + release: '1.4.0-preview.1', + environment: 'Test', + message: SentryMessage('This is an example Dart event.'), + tags: const {'project-id': '7371'}, + // ignore: deprecated_member_use, deprecated_member_use_from_same_package + extra: const {'section': '1'}, + // fingerprint: const ['example-dart'], + user: SentryUser( + id: '800', + username: 'first-user', + email: 'first@user.lan', + // ipAddress: '127.0.0.1', + data: {'first-sign-in': '2020-01-01'}, + ), + breadcrumbs: [ + Breadcrumb( + message: 'UI Lifecycle', + timestamp: DateTime.now().toUtc(), + category: 'ui.lifecycle', + type: 'navigation', + data: {'screen': 'MainActivity', 'state': 'created'}, + level: SentryLevel.info, + ) + ], + contexts: Contexts( + operatingSystem: SentryOperatingSystem( + name: 'Android', + version: '5.0.2', + build: 'LRX22G.P900XXS0BPL2', + kernelVersion: + 'Linux version 3.4.39-5726670 (dpi@SWHC3807) (gcc version 4.8 (GCC) ) #1 SMP PREEMPT Thu Dec 1 19:42:39 KST 2016', + rooted: false, + ), + runtimes: [SentryRuntime(name: 'ART', version: '5')], + app: SentryApp( + name: 'Example Dart App', + version: '1.42.0', + identifier: 'HGT-App-13', + build: '93785', + buildType: 'release', + deviceAppHash: '5afd3a6', + startTime: DateTime.now().toUtc(), + ), + browser: SentryBrowser(name: 'Firefox', version: '42.0.1'), + device: SentryDevice( + name: 'SM-P900', + family: 'SM-P900', + model: 'SM-P900 (LRX22G)', + modelId: 'LRX22G', + arch: 'armeabi-v7a', + batteryLevel: 99, + orientation: SentryOrientation.landscape, + manufacturer: 'samsung', + brand: 'samsung', + screenDensity: 2.1, + screenDpi: 320, + online: true, + charging: true, + lowMemory: true, + simulator: false, + memorySize: 1500, + freeMemory: 200, + usableMemory: 4294967296, + storageSize: 4294967296, + freeStorage: 2147483648, + externalStorageSize: 8589934592, + externalFreeStorage: 2863311530, + bootTime: DateTime.now().toUtc(), + ), + ), +); diff --git a/packages/dart/example_web/web/favicon.ico b/packages/dart/example_web/web/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7ba349b3e628d2423d4a2ed217422a4722f73739 GIT binary patch literal 3559 zcmV|z)fLATAcZDKyK$JdGY~s=NSr`PnS}BvP$+3A z8CpoogqBhg+p;Cg51fS9@izOF7~1r6zw|?g zDQ!X_8B4l7_wKH=QY>4NwW55uUP;#D-rxP7bI-kdjtU{9Dpi9&%XV3<*GkWK^P@NG zgWRw6Vb?`n$T_Evx_k{$?y0Rh-E#bYD?-UGV3Tc>$SdfYhb2dG)#K`(KPKx z4IwA0_p^z5A4{(AI%=BqUe-mpgFoo&TY*3Gu!0a29lR)aGV2dpEZ4z|Kc)+FUc-bN zHIDPB&TC8HnJ0tyG0*^nmzmQ?TnN+!QqapY^N|7@`F5AqbYw-`02pC0LNbv4yz60?w^9K&j_>533B&I%i9tFNIn5p2kb+@G0y43>@$)ns6>BLG63+2Wpepx zJ&v#ILasL(C%pe{n)2h>g2u-1wVpgKUaNE4V$J76NI&82+j&+}!O~12Z$~FRKK$`9 zx^J3f|L@(w z@^0VL;CU-=w^+ZF9FR4?4ODJ#62DZXnxe`qk)!2S9)0Z%YeH3TkE!aMNY!YE_0LhF z2ESF$qU+kcNYfp>Oq;_Knx0_qs&4=0WPdHW`-Qyher0=jx5gB?QhDMW+Qc1=t$k|< zt=eZtRI`&@>AfXtZFZz?wIfZ37txkUL?4_$0OBvSIr99C2j2UN)Ni@j77k#SApKPq z|7OZGK1&}QM-|70VjJzpQ8hDwD&8DI6m)83lM`v+s(Btdr*I>`(aIvtK1ZDD;A51L zClILKDAJgMZ)-X|x8@2VC+X9BJv40&^lN&j5M^{HDvl4q-~qts09^Y4!n4Ma6_Lw34kz1b@>qe;tZn9VPT9z@k+{b=Lo2to6L3;F~QIz4!D1T|P-qRdf7Z303(CYKm}t10))3j2!;|tzyS7gc;G1rFhS73B&NU|LN;}mYr{eivPfUF zdm~5DreHsX?W>bdsM|qmnE=2HBnZ`V2&GU0HiPHE4BB~d@G=O*FMxyW35}^c+*y^d zu=LHL8rmGaLUn`myIgTKc-?scBq8(@2<4?z0#?C(P6j}(1UFeFC{V&pSs-Nh`dIqC zkq_zKagZ2z+AcRzw=V!dgs?$W0)eov1WLdv*y|LWVW)c@2!awQQ^c0$7^MT+`37Is z%4jsE07!ol4_@%H1b}B@02vS}j=YN~fUrVwC4dzE;VS8yeRqJ(To9x$c>TNqWIDzpRz&Sr zPzjP57~P9Na0}*O4%=_+^52#;fi&rNW3NA+l7688GL>)?AiTgTsszmeR~7(L6O~|@ zzz|qG+3C{n4%C4}E>qpUB(Ws{kV9bm(b{8HL<58sjR2ud0W;XQkP4(=2|ILf=2+pq z(O1(09&`AwG{n*Q)qw$JVxnF zMFb%C2^hk0fN(%m0*265LNmZ)!wN7*KLbbq8UaA{1auJa2wp!^`o#huDPc4NLNR?p zE@mJB=mh`=BfnEomf&3wBwPRh_zkhFA1nrdt00_4bi2$P+KLn!cjN=0CupO3Leg$3 zp*Vm{2>k+tq!Nk%A+NXX^~lmZ}E0)ru(A`q6O1aeT4#SAh5kY%uwe*{*64`?9{h|TK{lms9t zVMO!^gQrlLafwQR&uH5D+yIa;xWn}w$_&dP-ZmCH63kNx)pmez0+e9HK7lI?Lbe@Z zCIIH03!8~Gbn zf+p*Bct|+_8A_;n`y?vsWCSI&<*x)yyDR;;ESm|WDWSu=9V-Fv4K$Kt?D8OWhX~-< z8M4JKx(QsRgh2tq34qYWSpHUUkm|e@h>8u?io3kMt+jNkPo$fU+`TO^E$=_ zAV@2L(Nh=zdBX|I7zlv)vLWhvxn(AR^nQB+a(@#wUK`rQ52NkQchOw{V?Bles;Gnx zuO~1Di)SVo=CHckmenU{((WCK0PvY$@A#*1=j-)CbAeSgo{@WXVb|Yr24@501Of;Q zgQUdn@s6RV_;ctHhZSwHy^XM+5McC+FpA(acq zkST#cFbNRUG6bnF(C#1)tpLs{oldkvBx7pL^j%9 z^aQ|o(0&Tt4lvfjK-P*ds`G^*Gl%u3PGSg&Ms9I z*zZ)`R3{W-EGbbsnIz4z4?~&D2QBA=kRHntC1hrXOE4OI7(xn09lZ7ozLsW{b=7 zbnCtL2cfv(eDh3zWQflPAv+AgOlsk^pSVZR4(AZM7hvEebZwgR987~DJRT$~4t`JN z@IV4P-6z6hXeZ}5TxI0SRjTv?3$ouKS*60hr&tvtLe{uv^Z_W4m}z-GL@GnHGIPk* zw6ctFod^P(OD!y`KXwnJ@4>QqH;FL@i7G0^fC~dyCpy$y;qkr9N%VyCOuRPafGQLB zzxU5Nx5-m}$bfT6kttLODx@M`to1wZ2XmNi7JNd^g%aAUV6e$$mBbisA;#D$#u!)` zw}J0?$bOnExiyeYuJhSrI5vUQ{Xnh5v4#|I^i3@pb{W7_{P2k5GK==kbAYr zd@D&R#;~Cu!m^6Z1Sv9BK^_RF-@KuRkuuEQ=LX6u&}L20<6F-P1JfjkL^$kk*d@$ZG_p zlDS-4dId>x;8Ix))Ft8KEW?C11O-;*xfWL`Qzk1{Ldf+^h!aB1=lxg-30(gpl+6{; zlAp7sn($go>tSNJPRTIkIh2%t4%H;e)d~Xy$^IHbwmS{eULGp}7eC>K>x%RdXHl9i z=pa>P`f>La2+w!sQ%|I9!8C>-&H_}9-U;=8E{GN8praR|_~}w{8h=S2<}S6&1}__C z{K0ykqcUgtgVR>NYFus(0ow+ctv$LRyQjfxf3DtV-(8H>5U@W7MVi`%u=AlE% + + + + + + + + + + + dart_web + + + + + + + + + + +
+ +
+ +
Captured
+
+ +
+ +
Captured
+
+ +
+ +
Captured
+
+ +
+ +
Captured
+
+ + + diff --git a/packages/dart/example_web/web/main.dart b/packages/dart/example_web/web/main.dart new file mode 100644 index 0000000000..7e349b7276 --- /dev/null +++ b/packages/dart/example_web/web/main.dart @@ -0,0 +1,150 @@ +import 'dart:async'; +import 'package:web/web.dart'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/version.dart'; + +import 'event.dart'; + +// ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io +const dsn = + 'https://e85b375ffb9f43cf8bdf9787768149e0@o447951.ingest.sentry.io/5428562'; + +Future main() async { + await Sentry.init( + (options) => options + ..dsn = dsn + ..debug = true + ..sendDefaultPii = true + ..addEventProcessor(TagEventProcessor()), + appRunner: runApp, + ); +} + +Future runApp() async { + print('runApp'); + + // ignore: deprecated_member_use + document.querySelector('#output')?.text = 'Your Dart app is running.'; + + await Sentry.addBreadcrumb( + Breadcrumb( + message: 'Authenticated user', + category: 'auth', + type: 'debug', + data: { + 'admin': true, + 'permissions': [1, 2, 3] + }, + ), + ); + + await Sentry.configureScope((scope) async { + scope + // ..fingerprint = ['example-dart'] + ..transaction = '/example/app' + ..level = SentryLevel.warning; + await scope.setTag('build', '579'); + // ignore: deprecated_member_use + await scope.setExtra('company-name', 'Dart Inc'); + + await scope.setUser( + SentryUser( + id: '800', + username: 'first-user', + email: 'first@user.lan', + // ipAddress: '127.0.0.1', + data: {'first-sign-in': '2020-01-01'}, + ), + ); + }); + + document + .querySelector('#btEvent') + ?.onClick + .listen((event) => captureCompleteExampleEvent()); + document + .querySelector('#btMessage') + ?.onClick + .listen((event) => captureMessage()); + document + .querySelector('#btException') + ?.onClick + .listen((event) => captureException()); + document + .querySelector('#btUnhandledException') + ?.onClick + .listen((event) => captureUnhandledException()); +} + +Future captureMessage() async { + print('Capturing Message : '); + final sentryId = await Sentry.captureMessage( + 'Message 2', + template: 'Message %s', + params: ['2'], + ); + print('capture message result : $sentryId'); + if (sentryId != SentryId.empty()) { + (document.querySelector('#messageResult') as HTMLElement?)?.style.display = + 'block'; + } +} + +Future captureException() async { + try { + await buildCard(); + } catch (error, stackTrace) { + print('\nReporting the following stack trace: '); + final sentryId = await Sentry.captureException( + error, + stackTrace: stackTrace, + ); + + print('Capture exception : SentryId: $sentryId'); + + if (sentryId != SentryId.empty()) { + (document.querySelector('#exceptionResult') as HTMLElement?) + ?.style + .display = 'block'; + } + } +} + +Future captureUnhandledException() async { + (document.querySelector('#unhandledResult') as HTMLElement?)?.style.display = + 'block'; + + await buildCard(); +} + +Future captureCompleteExampleEvent() async { + print('\nReporting a complete event example: $sdkName'); + final sentryId = await Sentry.captureEvent(event); + + print('Response SentryId: $sentryId'); + + if (sentryId != SentryId.empty()) { + (document.querySelector('#eventResult') as HTMLElement?)?.style.display = + 'block'; + } +} + +Future buildCard() async { + await loadData(); +} + +Future loadData() async { + await parseData(); +} + +Future parseData() async { + throw StateError('This is a test error'); +} + +class TagEventProcessor implements EventProcessor { + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + return event..tags?.addAll({'page-locale': 'en-us'}); + } +} diff --git a/packages/dart/example_web/web/styles.css b/packages/dart/example_web/web/styles.css new file mode 100644 index 0000000000..cc035c95c9 --- /dev/null +++ b/packages/dart/example_web/web/styles.css @@ -0,0 +1,14 @@ +@import url(https://fonts.googleapis.com/css?family=Roboto); + +html, body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + font-family: 'Roboto', sans-serif; +} + +#output { + padding: 20px; + text-align: center; +} diff --git a/packages/dart/lib/sentry.dart b/packages/dart/lib/sentry.dart new file mode 100644 index 0000000000..bb349a6a3c --- /dev/null +++ b/packages/dart/lib/sentry.dart @@ -0,0 +1,63 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// A pure Dart client for Sentry.io crash reporting. +library; + +// ignore: invalid_export_of_internal_element +export 'src/constants.dart'; +export 'src/event_processor.dart'; +export 'src/exception_cause.dart'; +export 'src/exception_cause_extractor.dart'; +export 'src/exception_stacktrace_extractor.dart'; +export 'src/exception_type_identifier.dart'; +export 'src/hint.dart'; +export 'src/http_client/sentry_http_client.dart'; +export 'src/http_client/sentry_http_client_error.dart'; +export 'src/hub.dart'; +export 'src/hub_adapter.dart'; +export 'src/integration.dart'; +export 'src/noop_isolate_error_integration.dart' + if (dart.library.io) 'src/isolate_error_integration.dart'; +// ignore: invalid_export_of_internal_element +export 'src/performance_collector.dart'; +export 'src/protocol.dart'; +export 'src/protocol/sentry_feature_flag.dart'; +export 'src/protocol/sentry_feature_flags.dart'; +export 'src/protocol/sentry_feedback.dart'; +export 'src/protocol/sentry_proxy.dart'; +export 'src/run_zoned_guarded_integration.dart'; +export 'src/runtime_checker.dart'; +export 'src/scope.dart'; +export 'src/scope_observer.dart'; +export 'src/sentry.dart'; +export 'src/sentry_attachment/sentry_attachment.dart'; +export 'src/sentry_baggage.dart'; +// ignore: invalid_export_of_internal_element +export 'src/sentry_client.dart'; +// ignore: invalid_export_of_internal_element +export 'src/sdk_lifecycle_hooks.dart'; +export 'src/sentry_envelope.dart'; +export 'src/sentry_envelope_item.dart'; +export 'src/sentry_options.dart'; +// ignore: invalid_export_of_internal_element +export 'src/sentry_trace_origins.dart'; +export 'src/span_data_convention.dart'; +export 'src/spotlight.dart'; +export 'src/throwable_mechanism.dart'; +export 'src/tracing.dart'; +export 'src/transport/transport.dart'; +export 'src/type_check_hint.dart'; +// ignore: invalid_export_of_internal_element +export 'src/utils.dart'; +// ignore: invalid_export_of_internal_element +export 'src/utils/http_header_utils.dart'; +// ignore: invalid_export_of_internal_element +export 'src/utils/http_sanitizer.dart'; +export 'src/utils/tracing_utils.dart'; +// ignore: invalid_export_of_internal_element +export 'src/utils/url_details.dart'; +// ignore: invalid_export_of_internal_element +export 'src/utils/breadcrumb_log_level.dart'; +export 'src/sentry_logger.dart'; diff --git a/packages/dart/lib/sentry_io.dart b/packages/dart/lib/sentry_io.dart new file mode 100644 index 0000000000..2c8eb91bde --- /dev/null +++ b/packages/dart/lib/sentry_io.dart @@ -0,0 +1,6 @@ +// ignore: invalid_export_of_internal_element +export 'sentry.dart'; +export 'src/sentry_attachment/io_sentry_attachment.dart'; +// Isolates +export 'src/sentry_isolate_extension.dart'; +export 'src/sentry_isolate.dart'; diff --git a/packages/dart/lib/src/client_reports/client_report.dart b/packages/dart/lib/src/client_reports/client_report.dart new file mode 100644 index 0000000000..347f8c7624 --- /dev/null +++ b/packages/dart/lib/src/client_reports/client_report.dart @@ -0,0 +1,30 @@ +import 'package:meta/meta.dart'; + +import '../utils.dart'; +import 'discarded_event.dart'; + +@internal +class ClientReport { + ClientReport(this.timestamp, this.discardedEvents); + + final DateTime? timestamp; + final List discardedEvents; + + Map toJson() { + final json = {}; + + if (timestamp != null) { + json['timestamp'] = formatDateAsIso8601WithMillisPrecision(timestamp!); + } + + final eventsJson = discardedEvents + .map((e) => e.toJson()) + .where((e) => e.isNotEmpty) + .toList(growable: false); + if (eventsJson.isNotEmpty) { + json['discarded_events'] = eventsJson; + } + + return json; + } +} diff --git a/packages/dart/lib/src/client_reports/client_report_recorder.dart b/packages/dart/lib/src/client_reports/client_report_recorder.dart new file mode 100644 index 0000000000..045168f0c5 --- /dev/null +++ b/packages/dart/lib/src/client_reports/client_report_recorder.dart @@ -0,0 +1,55 @@ +import 'package:meta/meta.dart'; + +import '../sentry_options.dart'; +import 'client_report.dart'; +import 'discarded_event.dart'; +import 'discard_reason.dart'; +import '../transport/data_category.dart'; + +@internal +class ClientReportRecorder { + ClientReportRecorder(this._clock); + + final ClockProvider _clock; + final Map<_QuantityKey, int> _quantities = {}; + + void recordLostEvent(final DiscardReason reason, final DataCategory category, + {int count = 1}) { + final key = _QuantityKey(reason, category); + var current = _quantities[key] ?? 0; + _quantities[key] = current + count; + } + + ClientReport? flush() { + if (_quantities.isEmpty) { + return null; + } + + final events = _quantities.keys.map((key) { + final quantity = _quantities[key] ?? 0; + return DiscardedEvent(key.reason, key.category, quantity); + }).toList(growable: false); + + _quantities.clear(); + + return ClientReport(_clock(), events); + } +} + +class _QuantityKey { + _QuantityKey(this.reason, this.category); + + final DiscardReason reason; + final DataCategory category; + + @override + int get hashCode => Object.hash(reason.hashCode, category.hashCode); + + @override + // ignore: non_nullable_equals_parameter + bool operator ==(dynamic other) { + return other is _QuantityKey && + other.reason == reason && + other.category == category; + } +} diff --git a/packages/dart/lib/src/client_reports/discard_reason.dart b/packages/dart/lib/src/client_reports/discard_reason.dart new file mode 100644 index 0000000000..df4de4b64c --- /dev/null +++ b/packages/dart/lib/src/client_reports/discard_reason.dart @@ -0,0 +1,15 @@ +import 'package:meta/meta.dart'; + +/// A reason that defines why events were lost, see +/// https://develop.sentry.dev/sdk/client-reports/#envelope-item-payload. +@internal +enum DiscardReason { + beforeSend, + eventProcessor, + sampleRate, + networkError, + queueOverflow, + cacheOverflow, + rateLimitBackoff, + ignored, +} diff --git a/packages/dart/lib/src/client_reports/discarded_event.dart b/packages/dart/lib/src/client_reports/discarded_event.dart new file mode 100644 index 0000000000..24a3471df0 --- /dev/null +++ b/packages/dart/lib/src/client_reports/discarded_event.dart @@ -0,0 +1,75 @@ +import 'package:meta/meta.dart'; + +import 'discard_reason.dart'; +import '../transport/data_category.dart'; + +@internal +class DiscardedEvent { + DiscardedEvent(this.reason, this.category, this.quantity); + + final DiscardReason reason; + final DataCategory category; + final int quantity; + + Map toJson() { + return { + 'reason': reason._toStringValue(), + 'category': category._toStringValue(), + 'quantity': quantity, + }; + } +} + +extension _OutcomeExtension on DiscardReason { + String _toStringValue() { + switch (this) { + case DiscardReason.beforeSend: + return 'before_send'; + case DiscardReason.eventProcessor: + return 'event_processor'; + case DiscardReason.sampleRate: + return 'sample_rate'; + case DiscardReason.networkError: + return 'network_error'; + case DiscardReason.queueOverflow: + return 'queue_overflow'; + case DiscardReason.cacheOverflow: + return 'cache_overflow'; + case DiscardReason.rateLimitBackoff: + return 'ratelimit_backoff'; + case DiscardReason.ignored: + return 'ignored'; + } + } +} + +extension _DataCategoryExtension on DataCategory { + String _toStringValue() { + switch (this) { + case DataCategory.all: + return '__all__'; + case DataCategory.dataCategoryDefault: + return 'default'; + case DataCategory.error: + return 'error'; + case DataCategory.session: + return 'session'; + case DataCategory.transaction: + return 'transaction'; + case DataCategory.span: + return 'span'; + case DataCategory.attachment: + return 'attachment'; + case DataCategory.security: + return 'security'; + case DataCategory.unknown: + return 'unknown'; + case DataCategory.logItem: + return 'log_item'; + case DataCategory.feedback: + return 'feedback'; + case DataCategory.metricBucket: + return 'metric_bucket'; + } + } +} diff --git a/packages/dart/lib/src/client_reports/noop_client_report_recorder.dart b/packages/dart/lib/src/client_reports/noop_client_report_recorder.dart new file mode 100644 index 0000000000..b03bd9c9ef --- /dev/null +++ b/packages/dart/lib/src/client_reports/noop_client_report_recorder.dart @@ -0,0 +1,20 @@ +import 'package:meta/meta.dart'; + +import '../transport/data_category.dart'; +import 'client_report.dart'; +import 'client_report_recorder.dart'; +import 'discard_reason.dart'; + +@internal +class NoOpClientReportRecorder implements ClientReportRecorder { + const NoOpClientReportRecorder(); + + @override + ClientReport? flush() { + return null; + } + + @override + void recordLostEvent(DiscardReason reason, DataCategory category, + {int count = 1}) {} +} diff --git a/packages/dart/lib/src/constants.dart b/packages/dart/lib/src/constants.dart new file mode 100644 index 0000000000..caa7641db0 --- /dev/null +++ b/packages/dart/lib/src/constants.dart @@ -0,0 +1,30 @@ +import 'package:meta/meta.dart'; + +@internal +class SentrySpanOperations { + static const String uiLoad = 'ui.load'; + static const String uiTimeToInitialDisplay = 'ui.load.initial_display'; + static const String uiTimeToFullDisplay = 'ui.load.full_display'; + static const String dbSqlQuery = 'db.sql.query'; + static const String dbSqlTransaction = 'db.sql.transaction'; + static const String dbSqlBatch = 'db.sql.batch'; + static const String dbOpen = 'db.open'; + static const String dbClose = 'db.close'; +} + +@internal +class SentrySpanData { + static const String dbSystemKey = 'db.system'; + static const String dbNameKey = 'db.name'; + + static const String dbSystemSqlite = 'db.sqlite'; +} + +@internal +class SentrySpanDescriptions { + static const String dbTransaction = 'Transaction'; + static String dbBatch({required List statements}) => + 'Batch $statements'; + static String dbOpen({required String dbName}) => 'Open database $dbName'; + static String dbClose({required String dbName}) => 'Close database $dbName'; +} diff --git a/packages/dart/lib/src/dart_exception_type_identifier.dart b/packages/dart/lib/src/dart_exception_type_identifier.dart new file mode 100644 index 0000000000..9f165ccba4 --- /dev/null +++ b/packages/dart/lib/src/dart_exception_type_identifier.dart @@ -0,0 +1,42 @@ +import 'dart:async' show TimeoutException, AsyncError, DeferredLoadException; + +import 'package:http/http.dart' show ClientException; + +import '../sentry.dart'; +import 'dart_exception_type_identifier_io.dart' + if (dart.library.js_interop) 'dart_exception_type_identifier_web.dart'; + +class DartExceptionTypeIdentifier implements ExceptionTypeIdentifier { + @override + String? identifyType(dynamic throwable) { + // dart:core + if (throwable is ArgumentError) return 'ArgumentError'; + if (throwable is AssertionError) return 'AssertionError'; + if (throwable is ConcurrentModificationError) { + return 'ConcurrentModificationError'; + } + if (throwable is FormatException) return 'FormatException'; + if (throwable is IndexError) return 'IndexError'; + if (throwable is NoSuchMethodError) return 'NoSuchMethodError'; + if (throwable is OutOfMemoryError) return 'OutOfMemoryError'; + if (throwable is RangeError) return 'RangeError'; + if (throwable is StackOverflowError) return 'StackOverflowError'; + if (throwable is StateError) return 'StateError'; + if (throwable is TypeError) return 'TypeError'; + if (throwable is UnimplementedError) return 'UnimplementedError'; + if (throwable is UnsupportedError) return 'UnsupportedError'; + // not adding Exception or Error because it's too generic + + // dart:async + if (throwable is TimeoutException) return 'TimeoutException'; + if (throwable is AsyncError) return 'FutureTimeout'; + if (throwable is DeferredLoadException) return 'DeferredLoadException'; + // not adding ParallelWaitError because it's not supported in dart 2.17.0 + + // dart http package + if (throwable is ClientException) return 'ClientException'; + + // platform specific exceptions + return identifyPlatformSpecificException(throwable); + } +} diff --git a/packages/dart/lib/src/dart_exception_type_identifier_io.dart b/packages/dart/lib/src/dart_exception_type_identifier_io.dart new file mode 100644 index 0000000000..1945663a01 --- /dev/null +++ b/packages/dart/lib/src/dart_exception_type_identifier_io.dart @@ -0,0 +1,14 @@ +import 'dart:io'; + +import 'package:meta/meta.dart'; + +@internal +String? identifyPlatformSpecificException(dynamic throwable) { + if (throwable is FileSystemException) return 'FileSystemException'; + if (throwable is HttpException) return 'HttpException'; + if (throwable is SocketException) return 'SocketException'; + if (throwable is HandshakeException) return 'HandshakeException'; + if (throwable is CertificateException) return 'CertificateException'; + if (throwable is TlsException) return 'TlsException'; + return null; +} diff --git a/packages/dart/lib/src/dart_exception_type_identifier_web.dart b/packages/dart/lib/src/dart_exception_type_identifier_web.dart new file mode 100644 index 0000000000..088ce9556e --- /dev/null +++ b/packages/dart/lib/src/dart_exception_type_identifier_web.dart @@ -0,0 +1,6 @@ +import 'package:meta/meta.dart'; + +@internal +String? identifyPlatformSpecificException(dynamic throwable) { + return null; +} diff --git a/packages/dart/lib/src/diagnostic_log.dart b/packages/dart/lib/src/diagnostic_log.dart new file mode 100644 index 0000000000..2379dafbc1 --- /dev/null +++ b/packages/dart/lib/src/diagnostic_log.dart @@ -0,0 +1,34 @@ +import 'protocol.dart'; +import 'sentry_options.dart'; + +class DiagnosticLog { + final SentryOptions _options; + final SdkLogCallback _logger; + SdkLogCallback get logger => _logger; + + DiagnosticLog(this._logger, this._options); + + void log( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + if (_isEnabled(level)) { + _logger( + level, + message, + logger: logger, + exception: exception, + stackTrace: stackTrace, + ); + } + } + + bool _isEnabled(SentryLevel level) { + return _options.debug && + level.ordinal >= _options.diagnosticLevel.ordinal || + level == SentryLevel.fatal; + } +} diff --git a/packages/dart/lib/src/environment/_io_environment_variables.dart b/packages/dart/lib/src/environment/_io_environment_variables.dart new file mode 100644 index 0000000000..93389afb83 --- /dev/null +++ b/packages/dart/lib/src/environment/_io_environment_variables.dart @@ -0,0 +1,24 @@ +import 'dart:io'; + +import '_web_environment_variables.dart'; +import 'environment_variables.dart'; +import 'keys.dart'; + +final EnvironmentVariables envs = IoEnvironmentVariables(); + +/// In addition to dart defines this io implementation can read from the +/// environment variables. +class IoEnvironmentVariables extends WebEnvironmentVariables { + @override + String? get environment => + super.environment ?? Platform.environment[sentryEnvironment]; + + @override + String? get dsn => super.dsn ?? Platform.environment[sentryDsn]; + + @override + String? get release => super.release ?? Platform.environment[sentryRelease]; + + @override + String? get dist => super.dist ?? Platform.environment[sentryDist]; +} diff --git a/packages/dart/lib/src/environment/_web_environment_variables.dart b/packages/dart/lib/src/environment/_web_environment_variables.dart new file mode 100644 index 0000000000..8721464a78 --- /dev/null +++ b/packages/dart/lib/src/environment/_web_environment_variables.dart @@ -0,0 +1,26 @@ +import 'environment_variables.dart'; +import 'keys.dart'; + +final EnvironmentVariables envs = WebEnvironmentVariables(); + +class WebEnvironmentVariables extends EnvironmentVariables { + @override + String? get environment => const bool.hasEnvironment(sentryEnvironment) + ? const String.fromEnvironment(sentryEnvironment) + : null; + + @override + String? get dsn => const bool.hasEnvironment(sentryDsn) + ? const String.fromEnvironment(sentryDsn) + : null; + + @override + String? get release => const bool.hasEnvironment(sentryRelease) + ? const String.fromEnvironment(sentryRelease) + : null; + + @override + String? get dist => const bool.hasEnvironment(sentryDist) + ? const String.fromEnvironment(sentryDist) + : null; +} diff --git a/packages/dart/lib/src/environment/environment_variables.dart b/packages/dart/lib/src/environment/environment_variables.dart new file mode 100644 index 0000000000..51c8b1ea0d --- /dev/null +++ b/packages/dart/lib/src/environment/environment_variables.dart @@ -0,0 +1,43 @@ +import '../runtime_checker.dart'; +import '_io_environment_variables.dart' + if (dart.library.js_interop) '_web_environment_variables.dart' as env; + +/// Reads environment variables from the system. +/// In an Flutter environment these can be set via +/// `flutter build --dart-define=VARIABLE_NAME=VARIABLE_VALUE`. +abstract class EnvironmentVariables { + factory EnvironmentVariables.instance() => env.envs; + + const EnvironmentVariables(); + + /// `SENTRY_ENVIRONMENT` + /// See [SentryOptions.environment] + String? get environment; + + /// `SENTRY_DSN` + /// See [SentryOptions.dsn] + String? get dsn; + + /// `SENTRY_RELEASE` + /// See [SentryOptions.release] + String? get release; + + /// `SENTRY_DIST` + /// See [SentryOptions.dist] + String? get dist; + + /// Returns an environment based on the compilation mode of Dart or Flutter. + /// This can be set as [SentryOptions.environment] + String environmentForMode(RuntimeChecker checker) { + // We infer the environment based on the release/non-release and profile + // constants. + + if (checker.isReleaseMode()) { + return 'production'; + } + if (checker.isProfileMode()) { + return 'profile'; + } + return 'debug'; + } +} diff --git a/packages/dart/lib/src/environment/keys.dart b/packages/dart/lib/src/environment/keys.dart new file mode 100644 index 0000000000..8ab8a0a11c --- /dev/null +++ b/packages/dart/lib/src/environment/keys.dart @@ -0,0 +1,6 @@ +// These are the keys which have to be set as environment variables or Dart +// defines in order to configure the corresponding Sentry setting +const sentryEnvironment = 'SENTRY_ENVIRONMENT'; +const sentryDsn = 'SENTRY_DSN'; +const sentryRelease = 'SENTRY_RELEASE'; +const sentryDist = 'SENTRY_DIST'; diff --git a/packages/dart/lib/src/event_processor.dart b/packages/dart/lib/src/event_processor.dart new file mode 100644 index 0000000000..45005d7b0e --- /dev/null +++ b/packages/dart/lib/src/event_processor.dart @@ -0,0 +1,14 @@ +import 'dart:async'; + +import 'hint.dart'; +import 'protocol.dart'; + +/// [EventProcessor]s are callbacks that run for every event. They can either +/// return a new event which in most cases means just adding data *or* return +/// null in case the event will be dropped and not sent. +abstract class EventProcessor { + FutureOr apply( + SentryEvent event, + Hint hint, + ); +} diff --git a/packages/dart/lib/src/event_processor/deduplication_event_processor.dart b/packages/dart/lib/src/event_processor/deduplication_event_processor.dart new file mode 100644 index 0000000000..48661257a6 --- /dev/null +++ b/packages/dart/lib/src/event_processor/deduplication_event_processor.dart @@ -0,0 +1,70 @@ +import 'dart:collection'; +import '../event_processor.dart'; +import '../hint.dart'; +import '../protocol.dart'; +import '../sentry_options.dart'; + +/// Deduplicates events with the same [SentryEvent.throwable]. +/// It keeps track of the last [SentryOptions.maxDeduplicationItems] +/// events. Older events aren't considered for deduplication. +/// +/// Only [SentryEvent]s where [SentryEvent.throwable] is not null are considered +/// for deduplication. [SentryEvent]s without exceptions aren't deduplicated. +/// +/// Caveats: +/// It does not work in the following case: +/// ```dart +/// var fooOne = Exception('foo'); +/// var fooTwo = Exception('foo'); +/// ``` +/// because (fooOne == fooTwo) equals false +class DeduplicationEventProcessor implements EventProcessor { + DeduplicationEventProcessor(this._options); + + // Using a HashSet makes this performant. + final Queue _exceptionToDeduplicate = Queue(); + final SentryOptions _options; + + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + if (event is SentryTransaction) { + return event; + } + + if (!_options.enableDeduplication) { + _options.log(SentryLevel.debug, 'Deduplication is disabled'); + return event; + } + return _deduplicate(event); + } + + SentryEvent? _deduplicate(SentryEvent event) { + // Cast to `Object?` in order to enable better type checking + // because `event.throwable` is `dynamic` + final exception = event.throwable as Object?; + + if (exception == null) { + // If no exception is given, just return the event + return event; + } + + // Just use the hashCode, to keep the memory footprint small + final exceptionHashCode = exception.hashCode; + + if (_exceptionToDeduplicate.contains(exceptionHashCode)) { + _options.log( + SentryLevel.info, + 'Duplicated exception detected. ' + 'Event ${event.eventId} will be discarded.', + ); + return null; + } + + // No duplication detected + _exceptionToDeduplicate.add(exceptionHashCode); + if (_exceptionToDeduplicate.length > _options.maxDeduplicationItems) { + _exceptionToDeduplicate.removeFirst(); + } + return event; + } +} diff --git a/packages/dart/lib/src/event_processor/enricher/enricher_event_processor.dart b/packages/dart/lib/src/event_processor/enricher/enricher_event_processor.dart new file mode 100644 index 0000000000..2b42b8f043 --- /dev/null +++ b/packages/dart/lib/src/event_processor/enricher/enricher_event_processor.dart @@ -0,0 +1,9 @@ +import '../../event_processor.dart'; +import '../../sentry_options.dart'; +import 'io_enricher_event_processor.dart' + if (dart.library.js_interop) 'web_enricher_event_processor.dart'; + +abstract class EnricherEventProcessor implements EventProcessor { + factory EnricherEventProcessor(SentryOptions options) => + enricherEventProcessor(options); +} diff --git a/packages/dart/lib/src/event_processor/enricher/flutter_runtime.dart b/packages/dart/lib/src/event_processor/enricher/flutter_runtime.dart new file mode 100644 index 0000000000..9c7a905194 --- /dev/null +++ b/packages/dart/lib/src/event_processor/enricher/flutter_runtime.dart @@ -0,0 +1,76 @@ +import '../../protocol/sentry_runtime.dart'; + +// The Flutter version information can be fetched via Dart defines, +// see +// - https://github.com/flutter/flutter/pull/140783 +// - https://github.com/flutter/flutter/pull/163761 +// +// This code lives in the Dart only Sentry code, since the code +// doesn't require any Flutter dependency. +// Additionally, this makes it work on background isolates in +// Flutter, where one may not initialize the whole Flutter Sentry +// SDK. +// The const-ness of the properties below ensure that the code +// is tree shaken in a non-Flutter environment. + +const _isFlutterRuntimeInformationAbsent = FlutterVersion.version == null || + FlutterVersion.channel == null || + FlutterVersion.frameworkRevision == null; + +final SentryRuntime? flutterRuntime = _isFlutterRuntimeInformationAbsent + ? null + : SentryRuntime( + name: 'Flutter', + version: '${FlutterVersion.version} (${FlutterVersion.channel})', + build: FlutterVersion.frameworkRevision, + rawDescription: '${FlutterVersion.version} (${FlutterVersion.channel}) ' + '- Git hash ${FlutterVersion.frameworkRevision} ' + '- Git URL ${FlutterVersion.gitUrl}', + ); + +final SentryRuntime? dartFlutterRuntime = FlutterVersion.dartVersion == null + ? null + : SentryRuntime(name: 'Dart', version: FlutterVersion.dartVersion); + +/// Details about the Flutter version this app was compiled with, +/// corresponding to the output of `flutter --version`. +/// +/// When this Flutter version was build from a fork, or when Flutter runs in a +/// custom embedder, these values might be unreliable. +abstract class FlutterVersion { + const FlutterVersion._(); + + /// The Flutter version used to compile the app. + static const String? version = bool.hasEnvironment('FLUTTER_VERSION') + ? String.fromEnvironment('FLUTTER_VERSION') + : null; + + /// The Flutter channel used to compile the app. + static const String? channel = bool.hasEnvironment('FLUTTER_CHANNEL') + ? String.fromEnvironment('FLUTTER_CHANNEL') + : null; + + /// The URL of the Git repository from which Flutter was obtained. + static const String? gitUrl = bool.hasEnvironment('FLUTTER_GIT_URL') + ? String.fromEnvironment('FLUTTER_GIT_URL') + : null; + + /// The Flutter framework revision, as a (short) Git commit ID. + static const String? frameworkRevision = + bool.hasEnvironment('FLUTTER_FRAMEWORK_REVISION') + ? String.fromEnvironment('FLUTTER_FRAMEWORK_REVISION') + : null; + + /// The Flutter engine revision. + static const String? engineRevision = + bool.hasEnvironment('FLUTTER_ENGINE_REVISION') + ? String.fromEnvironment('FLUTTER_ENGINE_REVISION') + : null; + + // This is included since [Platform.version](https://api.dart.dev/stable/dart-io/Platform/version.html) + // is not included on web platforms. + /// The Dart version used to compile the app. + static const String? dartVersion = bool.hasEnvironment('FLUTTER_DART_VERSION') + ? String.fromEnvironment('FLUTTER_DART_VERSION') + : null; +} diff --git a/packages/dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart b/packages/dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart new file mode 100644 index 0000000000..288d047700 --- /dev/null +++ b/packages/dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart @@ -0,0 +1,142 @@ +import 'dart:io'; + +import '../../../sentry.dart'; +import 'enricher_event_processor.dart'; +import 'flutter_runtime.dart'; +import 'io_platform_memory.dart'; +import '../../utils/os_utils.dart'; + +EnricherEventProcessor enricherEventProcessor(SentryOptions options) { + return IoEnricherEventProcessor(options); +} + +/// Enriches [SentryEvent]s with various kinds of information. +/// Uses Darts [Platform](https://api.dart.dev/stable/dart-io/Platform-class.html) +/// class to read information. +class IoEnricherEventProcessor implements EnricherEventProcessor { + IoEnricherEventProcessor(this._options); + + final SentryOptions _options; + late final String _dartVersion = _extractDartVersion(Platform.version); + late final SentryOperatingSystem _os = getSentryOperatingSystem(); + bool _fetchedTotalPhysicalMemory = false; + int? _totalPhysicalMemory; + + /// Extracts the semantic version and channel from the full version string. + /// + /// Example: + /// Input: "3.5.0-180.3.beta (beta) (Wed Jun 5 15:06:15 2024 +0000) on "android_arm64"" + /// Output: "3.5.0-180.3.beta (beta)" + /// + /// Falls back to the full version if the matching fails. + String _extractDartVersion(String fullVersion) { + RegExp channelRegex = RegExp(r'\((stable|beta|dev)\)'); + Match? match = channelRegex.firstMatch(fullVersion); + // if match is null this will return the full version + return fullVersion.substring(0, match?.end); + } + + @override + Future apply(SentryEvent event, Hint hint) async { + event.contexts + ..device = await _getDevice(event.contexts.device) + ..operatingSystem = _getOperatingSystem(event.contexts.operatingSystem) + ..runtimes = _getRuntimes(event.contexts.runtimes) + ..app = _getApp(event.contexts.app) + ..culture = _getSentryCulture(event.contexts.culture); + + event.contexts['dart_context'] = _getDartContext(); + return event; + } + + List _getRuntimes(List? runtimes) { + // Pure Dart doesn't have specific runtimes per build mode + // like Flutter: https://flutter.dev/docs/testing/build-modes + final dartRuntime = SentryRuntime( + name: 'Dart', + version: _dartVersion, + rawDescription: Platform.version, + ); + final flRuntime = flutterRuntime; + + if (runtimes == null) { + return [dartRuntime, if (flRuntime != null) flRuntime]; + } + return [ + ...runtimes, + dartRuntime, + if (flRuntime != null) flRuntime, + ]; + } + + Map _getDartContext() { + final args = Platform.executableArguments; + final packageConfig = Platform.packageConfig; + + String? executable; + if (_options.sendDefaultPii) { + executable = Platform.executable; + } + + return { + 'compile_mode': _options.runtimeChecker.compileMode, + if (packageConfig != null) 'package_config': packageConfig, + // The following information could potentially contain PII + if (_options.sendDefaultPii) ...{ + 'executable': executable, + 'resolved_executable': Platform.resolvedExecutable, + 'script': Platform.script.toString(), + if (args.isNotEmpty) + 'executable_arguments': Platform.executableArguments, + }, + }; + } + + Future _getDevice(SentryDevice? device) async { + device ??= SentryDevice(); + return device + ..name = device.name ?? + (_options.sendDefaultPii ? Platform.localHostname : null) + ..processorCount = device.processorCount ?? Platform.numberOfProcessors + ..memorySize = device.memorySize ?? await _getTotalPhysicalMemory() + ..freeMemory = device.freeMemory; + } + + Future _getTotalPhysicalMemory() async { + if (!_fetchedTotalPhysicalMemory) { + _totalPhysicalMemory = + await PlatformMemory(_options).getTotalPhysicalMemory(); + _fetchedTotalPhysicalMemory = true; + } + return _totalPhysicalMemory; + } + + SentryApp _getApp(SentryApp? app) { + app ??= SentryApp(); + return app..appMemory = app.appMemory ?? ProcessInfo.currentRss; + } + + SentryOperatingSystem _getOperatingSystem(SentryOperatingSystem? os) { + if (os == null) { + return SentryOperatingSystem( + name: _os.name, + version: _os.version, + build: _os.build, + kernelVersion: _os.kernelVersion, + rooted: _os.rooted, + rawDescription: _os.rawDescription, + theme: _os.theme, + unknown: _os.unknown, + ); + } else { + return _os.mergeWith(os); + } + } + + SentryCulture _getSentryCulture(SentryCulture? culture) { + culture ??= SentryCulture(); + return culture + ..locale = culture.locale ?? Platform.localeName + ..timezone = culture.timezone ?? DateTime.now().timeZoneName; + } +} diff --git a/packages/dart/lib/src/event_processor/enricher/io_platform_memory.dart b/packages/dart/lib/src/event_processor/enricher/io_platform_memory.dart new file mode 100644 index 0000000000..89a0a31ee7 --- /dev/null +++ b/packages/dart/lib/src/event_processor/enricher/io_platform_memory.dart @@ -0,0 +1,143 @@ +import 'dart:io'; + +import '../../protocol.dart'; +import '../../sentry_options.dart'; + +// Get total & free platform memory (in bytes) for linux and windows operating systems. +// Source: https://github.com/onepub-dev/system_info/blob/8a9bf6b8eb7c86a09b3c3df4bf6d7fa5a6b50732/lib/src/platform/memory.dart +class PlatformMemory { + PlatformMemory(this.options) { + if (options.platform.isWindows) { + // Check for WMIC (deprecated in newer Windows versions) + // https://techcommunity.microsoft.com/blog/windows-itpro-blog/wmi-command-line-wmic-utility-deprecation-next-steps/4039242 + useWindowsWmci = + File('C:\\Windows\\System32\\wbem\\wmic.exe').existsSync(); + if (!useWindowsWmci) { + useWindowsPowerShell = File( + 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe') + .existsSync(); + } else { + useWindowsPowerShell = false; + } + } else { + useWindowsWmci = false; + useWindowsPowerShell = false; + } + } + + final SentryOptions options; + late final bool useWindowsWmci; + late final bool useWindowsPowerShell; + + Future getTotalPhysicalMemory() async { + if (options.platform.isLinux) { + return _getLinuxMemInfoValue('MemTotal'); + } else if (options.platform.isWindows) { + if (useWindowsWmci) { + return _getWindowsWmicValue('ComputerSystem', 'TotalPhysicalMemory'); + } else if (useWindowsPowerShell) { + return _getWindowsPowershellTotalMemoryValue(); + } else { + return null; + } + } else { + return null; + } + } + + Future _getWindowsWmicValue(String section, String key) async { + final os = await _wmicGetValueAsMap(section, [key]); + final totalPhysicalMemoryValue = os?[key]; + if (totalPhysicalMemoryValue == null) { + return null; + } + final size = int.tryParse(totalPhysicalMemoryValue); + if (size == null) { + return null; + } + return size; + } + + Future _getLinuxMemInfoValue(String key) async { + final result = await _exec('cat', ['/proc/meminfo']); + final meminfoList = + result?.trim().replaceAll('\r\n', '\n').split('\n') ?? []; + + final meminfoMap = _listToMap(meminfoList, ':'); + final memsizeResults = meminfoMap[key]?.split(' ') ?? []; + + if (memsizeResults.isEmpty) { + return null; + } + final memsizeResult = memsizeResults.first; + + final memsize = int.tryParse(memsizeResult); + if (memsize == null) { + return null; + } + return memsize; + } + + Future _exec(String executable, List arguments, + {bool runInShell = false}) async { + try { + final result = + await Process.run(executable, arguments, runInShell: runInShell); + if (result.exitCode == 0) { + return result.stdout.toString(); + } + } catch (e) { + options.log(SentryLevel.warning, "Failed to run process: $e"); + if (options.automatedTestMode) { + rethrow; + } + } + return null; + } + + Future?> _wmicGetValueAsMap( + String section, List fields) async { + final arguments = [section]; + arguments + ..add('get') + ..addAll(fields.join(', ').split(' ')) + ..add('/VALUE'); + + final result = await _exec('wmic', arguments); + final list = result?.trim().replaceAll('\r\n', '\n').split('\n') ?? []; + + return _listToMap(list, '='); + } + + Map _listToMap(List list, String separator) { + final map = {}; + for (final string in list) { + final index = string.indexOf(separator); + if (index != -1) { + final key = string.substring(0, index).trim(); + final value = string.substring(index + 1).trim(); + map[key] = value; + } + } + return map; + } + + Future _getWindowsPowershellTotalMemoryValue() async { + final command = + 'Get-CimInstance Win32_ComputerSystem | Select-Object -ExpandProperty TotalPhysicalMemory'; + + final result = await _exec('powershell.exe', + ['-NoProfile', '-NonInteractive', '-Command', command]); + if (result == null) { + return null; + } + + final value = result.trim(); + final size = int.tryParse(value); + if (size == null) { + return null; + } + + return size; + } +} diff --git a/packages/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart b/packages/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart new file mode 100644 index 0000000000..e1a73612d2 --- /dev/null +++ b/packages/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart @@ -0,0 +1,122 @@ +import 'package:web/web.dart' as web show window, Window, Navigator; + +import '../../../sentry.dart'; +import 'enricher_event_processor.dart'; +import 'flutter_runtime.dart'; + +EnricherEventProcessor enricherEventProcessor(SentryOptions options) { + return WebEnricherEventProcessor( + web.window, + options, + ); +} + +class WebEnricherEventProcessor implements EnricherEventProcessor { + WebEnricherEventProcessor( + this._window, + this._options, + ); + + final web.Window _window; + + final SentryOptions _options; + + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + // Web has no native integration, so no need to check for it + event.contexts + ..device = _getDevice(event.contexts.device) + ..culture = _getSentryCulture(event.contexts.culture) + ..runtimes = _getRuntimes(event.contexts.runtimes); + + event.contexts['dart_context'] = _getDartContext(); + + return event + ..request = _getRequest(event.request) + ..transaction = event.transaction ?? _window.location.pathname; + } + + // As seen in + // https://github.com/getsentry/sentry-javascript/blob/a6f8dc26a4c7ae2146ae64995a2018c8578896a6/packages/browser/src/integrations/useragent.ts + SentryRequest _getRequest(SentryRequest? request) { + final requestHeader = request?.headers; + final header = requestHeader == null + ? {} + : Map.from(requestHeader); + + header.putIfAbsent('User-Agent', () => _window.navigator.userAgent); + + final url = request?.url ?? _window.location.toString(); + request ??= SentryRequest(url: url); + return request + ..headers = header + ..sanitize(); + } + + SentryDevice _getDevice(SentryDevice? device) { + device ??= SentryDevice(); + return device + ..online = device.online ?? _window.navigator.onLine + ..memorySize = device.memorySize ?? _getMemorySize() + ..orientation = device.orientation ?? _getScreenOrientation() + ..screenHeightPixels = + device.screenHeightPixels ?? _window.screen.availHeight + ..screenWidthPixels = + device.screenWidthPixels ?? _window.screen.availWidth + ..screenDensity = + device.screenDensity ?? _window.devicePixelRatio.toDouble(); + } + + int? _getMemorySize() { + // https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory + // ignore: invalid_null_aware_operator + final size = _window.navigator.deviceMemory?.toDouble(); + final memoryByteSize = size != null ? size * 1024 * 1024 * 1024 : null; + return memoryByteSize?.toInt(); + } + + SentryOrientation? _getScreenOrientation() { + // https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation + final screenOrientation = _window.screen.orientation; + if (screenOrientation.type.startsWith('portrait')) { + return SentryOrientation.portrait; + } + if (screenOrientation.type.startsWith('landscape')) { + return SentryOrientation.landscape; + } + return null; + } + + Map _getDartContext() { + return { + 'compile_mode': _options.runtimeChecker.compileMode, + }; + } + + SentryCulture _getSentryCulture(SentryCulture? culture) { + culture ??= SentryCulture(); + return culture..timezone = culture.timezone ?? DateTime.now().timeZoneName; + } + + List _getRuntimes(List? runtimes) { + final flRuntime = flutterRuntime; + final dartFlRuntime = dartFlutterRuntime; + + if (runtimes == null) { + return [ + if (flRuntime != null) flRuntime, + if (dartFlRuntime != null) dartFlRuntime, + ]; + } + return [ + ...runtimes, + if (flRuntime != null) flRuntime, + if (dartFlRuntime != null) dartFlRuntime, + ]; + } +} + +extension on web.Navigator { + // ignore: unused_element + external double? get deviceMemory; +} diff --git a/packages/dart/lib/src/event_processor/exception/exception_event_processor.dart b/packages/dart/lib/src/event_processor/exception/exception_event_processor.dart new file mode 100644 index 0000000000..6e65c72391 --- /dev/null +++ b/packages/dart/lib/src/event_processor/exception/exception_event_processor.dart @@ -0,0 +1,9 @@ +import '../../event_processor.dart'; +import '../../sentry_options.dart'; +import 'io_exception_event_processor.dart' + if (dart.library.js_interop) 'web_exception_event_processor.dart'; + +abstract class ExceptionEventProcessor implements EventProcessor { + factory ExceptionEventProcessor(SentryOptions options) => + exceptionEventProcessor(options); +} diff --git a/packages/dart/lib/src/event_processor/exception/exception_group_event_processor.dart b/packages/dart/lib/src/event_processor/exception/exception_group_event_processor.dart new file mode 100644 index 0000000000..f0b22d66d6 --- /dev/null +++ b/packages/dart/lib/src/event_processor/exception/exception_group_event_processor.dart @@ -0,0 +1,68 @@ +import '../../event_processor.dart'; +import '../../protocol.dart'; +import '../../hint.dart'; +import '../../sentry_options.dart'; + +/// Group exceptions into a flat list with references to hierarchy. +class ExceptionGroupEventProcessor implements EventProcessor { + final SentryOptions _options; + + ExceptionGroupEventProcessor(this._options); + + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + final sentryExceptions = event.exceptions ?? []; + if (sentryExceptions.isEmpty) { + return event; + } + final firstException = sentryExceptions.first; + + if (sentryExceptions.length > 1 || firstException.exceptions == null) { + // If already a list or no child exceptions, no grouping possible/needed. + return event; + } else { + if (_options.groupExceptions) { + event.exceptions = firstException + .flatten(groupExceptions: true) + .reversed + .toList(growable: false); + } else { + event.exceptions = firstException.flatten(groupExceptions: false); + } + return event; + } + } +} + +extension _SentryExceptionFlatten on SentryException { + List flatten( + {int? parentId, int id = 0, required bool groupExceptions}) { + final exceptions = this.exceptions ?? []; + + if (groupExceptions) { + final newMechanism = mechanism ?? Mechanism(type: "generic"); + newMechanism + ..type = id > 0 ? "chained" : newMechanism.type + ..parentId = parentId + ..exceptionId = id + ..isExceptionGroup = exceptions.isNotEmpty ? true : null; + + mechanism = newMechanism; + } + + var all = []; + all.add(this); + + if (exceptions.isNotEmpty) { + final parentId = id; + for (var exception in exceptions) { + id++; + final flattenedExceptions = exception.flatten( + parentId: parentId, id: id, groupExceptions: groupExceptions); + id = flattenedExceptions.lastOrNull?.mechanism?.exceptionId ?? id; + all.addAll(flattenedExceptions); + } + } + return all.toList(growable: false); + } +} diff --git a/packages/dart/lib/src/event_processor/exception/io_exception_event_processor.dart b/packages/dart/lib/src/event_processor/exception/io_exception_event_processor.dart new file mode 100644 index 0000000000..1b698d5c7a --- /dev/null +++ b/packages/dart/lib/src/event_processor/exception/io_exception_event_processor.dart @@ -0,0 +1,127 @@ +import 'dart:io'; + +import '../../hint.dart'; +import '../../protocol.dart'; +import '../../sentry_options.dart'; +import 'exception_event_processor.dart'; + +ExceptionEventProcessor exceptionEventProcessor(SentryOptions options) => + IoExceptionEventProcessor(options); + +class IoExceptionEventProcessor implements ExceptionEventProcessor { + IoExceptionEventProcessor(this._options); + + final SentryOptions _options; + + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + final throwable = event.throwable; + if (throwable is HttpException) { + return _applyHttpException(throwable, event); + } + if (throwable is SocketException) { + return _applySocketException(throwable, event); + } + if (throwable is FileSystemException) { + return _applyFileSystemException(throwable, event); + } + + return event; + } + + // https://api.dart.dev/stable/dart-io/HttpException-class.html + SentryEvent _applyHttpException(HttpException exception, SentryEvent event) { + final uri = exception.uri; + if (uri == null) { + return event; + } + return event..request = event.request ?? SentryRequest.fromUri(uri: uri); + } + + // https://api.dart.dev/stable/dart-io/SocketException-class.html + SentryEvent _applySocketException( + SocketException exception, + SentryEvent event, + ) { + final osError = exception.osError; + SentryException? osException; + List? exceptions = event.exceptions; + if (osError != null) { + // OSError is the underlying error + // https://api.dart.dev/stable/dart-io/SocketException/osError.html + // https://api.dart.dev/stable/dart-io/OSError-class.html + osException = _sentryExceptionFromOsError(osError); + final exception = event.exceptions?.firstOrNull; + if (exception != null) { + exception.addException(osException); + } else { + exceptions = [osException]; + } + } else { + exceptions = event.exceptions; + } + + final address = exception.address; + if (address == null) { + event.exceptions = exceptions; + return event; + } + SentryRequest? request; + try { + var uri = Uri.parse(address.host); + request = SentryRequest.fromUri(uri: uri); + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'Could not parse ${address.host} to Uri', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } + + return event + ..request = event.request ?? request + ..exceptions = exceptions; + } + + // https://api.dart.dev/stable/dart-io/FileSystemException-class.html + SentryEvent _applyFileSystemException( + FileSystemException exception, + SentryEvent event, + ) { + final osError = exception.osError; + + if (osError != null) { + // OSError is the underlying error + // https://api.dart.dev/stable/dart-io/SocketException/osError.html + // https://api.dart.dev/stable/dart-io/OSError-class.html + final osException = _sentryExceptionFromOsError(osError); + final exception = event.exceptions?.firstOrNull; + if (exception != null) { + exception.addException(osException); + } else { + event.exceptions = [osException]; + } + } + return event; + } +} + +SentryException _sentryExceptionFromOsError(OSError osError) { + return SentryException( + type: osError.runtimeType.toString(), + value: osError.toString(), + // osError.errorCode is likely a posix signal + // https://develop.sentry.dev/sdk/event-payloads/types/#mechanismmeta + mechanism: Mechanism( + type: 'OSError', + meta: { + 'errno': {'number': osError.errorCode}, + }, + source: 'osError', + ), + ); +} diff --git a/packages/dart/lib/src/event_processor/exception/web_exception_event_processor.dart b/packages/dart/lib/src/event_processor/exception/web_exception_event_processor.dart new file mode 100644 index 0000000000..6ce3be0fe0 --- /dev/null +++ b/packages/dart/lib/src/event_processor/exception/web_exception_event_processor.dart @@ -0,0 +1,12 @@ +import '../../hint.dart'; +import '../../protocol.dart'; +import '../../sentry_options.dart'; +import 'exception_event_processor.dart'; + +ExceptionEventProcessor exceptionEventProcessor(SentryOptions _) => + WebExcptionEventProcessor(); + +class WebExcptionEventProcessor implements ExceptionEventProcessor { + @override + SentryEvent apply(SentryEvent event, Hint hint) => event; +} diff --git a/packages/dart/lib/src/event_processor/run_event_processors.dart b/packages/dart/lib/src/event_processor/run_event_processors.dart new file mode 100644 index 0000000000..9af583a12a --- /dev/null +++ b/packages/dart/lib/src/event_processor/run_event_processors.dart @@ -0,0 +1,79 @@ +import 'package:meta/meta.dart'; + +import '../client_reports/discard_reason.dart'; +import '../event_processor.dart'; +import '../hint.dart'; +import '../protocol/sentry_event.dart'; +import '../protocol/sentry_level.dart'; +import '../protocol/sentry_transaction.dart'; +import '../sentry_options.dart'; +import '../transport/data_category.dart'; + +@internal +Future runEventProcessors( + SentryEvent event, + Hint hint, + List eventProcessors, + SentryOptions options, +) async { + int spanCountBeforeEventProcessors = + event is SentryTransaction ? event.spans.length : 0; + + SentryEvent? processedEvent = event; + for (final processor in eventProcessors) { + try { + final e = processor.apply(processedEvent!, hint); + processedEvent = e is Future ? await e : e; + } catch (exception, stackTrace) { + options.log( + SentryLevel.error, + 'An exception occurred while processing event by a processor', + exception: exception, + stackTrace: stackTrace, + ); + if (options.automatedTestMode) { + rethrow; + } + } + + final discardReason = DiscardReason.eventProcessor; + if (processedEvent == null) { + options.recorder.recordLostEvent(discardReason, _getCategory(event)); + if (event is SentryTransaction) { + // We dropped the whole transaction, the dropped count includes all child spans + 1 root span + options.recorder.recordLostEvent( + discardReason, + DataCategory.span, + count: spanCountBeforeEventProcessors + 1, + ); + } + options.log(SentryLevel.debug, 'Event was dropped by a processor'); + break; + } else if (event is SentryTransaction && + processedEvent is SentryTransaction) { + // If event processor removed only some spans we still report them as dropped + final spanCountAfterEventProcessors = processedEvent.spans.length; + final droppedSpanCount = + spanCountBeforeEventProcessors - spanCountAfterEventProcessors; + if (droppedSpanCount > 0) { + options.recorder.recordLostEvent( + discardReason, + DataCategory.span, + count: droppedSpanCount, + ); + } + } + } + + return processedEvent; +} + +DataCategory _getCategory(SentryEvent event) { + if (event is SentryTransaction) { + return DataCategory.transaction; + } else if (event.type == 'feedback') { + return DataCategory.feedback; + } else { + return DataCategory.error; + } +} diff --git a/packages/dart/lib/src/exception_cause.dart b/packages/dart/lib/src/exception_cause.dart new file mode 100644 index 0000000000..60bf12e31e --- /dev/null +++ b/packages/dart/lib/src/exception_cause.dart @@ -0,0 +1,8 @@ +/// Holds inner exception and stackTrace combinations contained in other exceptions +class ExceptionCause { + ExceptionCause(this.exception, this.stackTrace, {this.source}); + + dynamic exception; + dynamic stackTrace; + String? source; +} diff --git a/packages/dart/lib/src/exception_cause_extractor.dart b/packages/dart/lib/src/exception_cause_extractor.dart new file mode 100644 index 0000000000..2913ac18bc --- /dev/null +++ b/packages/dart/lib/src/exception_cause_extractor.dart @@ -0,0 +1,35 @@ +import 'protocol.dart'; +import 'exception_cause.dart'; +import 'sentry_options.dart'; + +/// Extend this abstract class and return inner [ExceptionCause] of your +/// exceptions. +/// +/// Implementing an extractor and providing it through +/// [SentryOptions.addExceptionCauseExtractor] will enable the framework to +/// extract the inner exceptions and add them as [SentryException] to +/// [SentryEvent.exceptions]. +/// +/// For an example on how to use the API refer to dio/DioErrorExtractor or the +/// code below: +/// +/// ```dart +/// class ExceptionWithInner { +/// ExceptionWithInner(this.innerException, this.innerStackTrace); +/// Object innerException; +/// StackTrace innerStackTrace; +/// } +/// +/// class ExceptionWithInnerExtractor extends ExceptionCauseExtractor { +/// @override +/// ExceptionCause? cause(ExceptionWithInner error) { +/// return ExceptionCause(error.innerException, error.innerStackTrace); +/// } +/// } +/// +/// options.addExceptionCauseExtractor(ExceptionWithInnerExtractor()); +/// ``` +abstract class ExceptionCauseExtractor { + ExceptionCause? cause(T error); + Type get exceptionType => T; +} diff --git a/packages/dart/lib/src/exception_stacktrace_extractor.dart b/packages/dart/lib/src/exception_stacktrace_extractor.dart new file mode 100644 index 0000000000..bf7b785bb6 --- /dev/null +++ b/packages/dart/lib/src/exception_stacktrace_extractor.dart @@ -0,0 +1,35 @@ +import 'protocol.dart'; +import 'sentry_options.dart'; + +/// Sentry handles [Error.stackTrace] by default. For other cases +/// extend this abstract class and return a custom [StackTrace] of your +/// exceptions. +/// +/// Implementing an extractor and providing it through +/// [SentryOptions.addExceptionStackTraceExtractor] will enable the framework to +/// extract the inner stacktrace and add it to [SentryException] when no other +/// stacktrace was provided while capturing the event. +/// +/// For an example on how to use the API refer to dio/DioStackTraceExtractor or the +/// code below: +/// +/// ```dart +/// class ExceptionWithInner { +/// ExceptionWithInner(this.innerException, this.innerStackTrace); +/// Object innerException; +/// dynamic innerStackTrace; +/// } +/// +/// class ExceptionWithInnerStackTraceExtractor extends ExceptionStackTraceExtractor { +/// @override +/// dynamic cause(ExceptionWithInner error) { +/// return error.innerStackTrace; +/// } +/// } +/// +/// options.addExceptionStackTraceExtractor(ExceptionWithInnerStackTraceExtractor()); +/// ``` +abstract class ExceptionStackTraceExtractor { + dynamic stackTrace(T error); + Type get exceptionType => T; +} diff --git a/packages/dart/lib/src/exception_type_identifier.dart b/packages/dart/lib/src/exception_type_identifier.dart new file mode 100644 index 0000000000..7dae9db841 --- /dev/null +++ b/packages/dart/lib/src/exception_type_identifier.dart @@ -0,0 +1,54 @@ +import 'package:meta/meta.dart'; + +/// An abstract class for identifying the type of Dart errors and exceptions. +/// +/// It's used in scenarios where error types need to be determined in obfuscated builds +/// as [runtimeType] is not reliable in such cases. +/// +/// Implement this class to create custom error type identifiers for errors or exceptions. +/// that we do not support out of the box. +/// +/// Example: +/// ```dart +/// class MyExceptionTypeIdentifier implements ExceptionTypeIdentifier { +/// @override +/// String? identifyType(dynamic throwable) { +/// if (throwable is MyCustomError) return 'MyCustomError'; +/// return null; +/// } +/// } +/// ``` +abstract class ExceptionTypeIdentifier { + String? identifyType(dynamic throwable); +} + +extension CacheableExceptionIdentifier on ExceptionTypeIdentifier { + ExceptionTypeIdentifier withCache() => CachingExceptionTypeIdentifier(this); +} + +@visibleForTesting +class CachingExceptionTypeIdentifier implements ExceptionTypeIdentifier { + @visibleForTesting + ExceptionTypeIdentifier get identifier => _identifier; + final ExceptionTypeIdentifier _identifier; + + final Map _knownExceptionTypes = {}; + + CachingExceptionTypeIdentifier(this._identifier); + + @override + String? identifyType(dynamic throwable) { + final runtimeType = throwable.runtimeType; + if (_knownExceptionTypes.containsKey(runtimeType)) { + return _knownExceptionTypes[runtimeType]; + } + + final identifiedType = _identifier.identifyType(throwable); + + if (identifiedType != null) { + _knownExceptionTypes[runtimeType] = identifiedType; + } + + return identifiedType; + } +} diff --git a/packages/dart/lib/src/feature_flags_integration.dart b/packages/dart/lib/src/feature_flags_integration.dart new file mode 100644 index 0000000000..4a19d16092 --- /dev/null +++ b/packages/dart/lib/src/feature_flags_integration.dart @@ -0,0 +1,45 @@ +import 'dart:async'; + +import 'hub.dart'; +import 'integration.dart'; +import 'sentry_options.dart'; +import 'protocol/sentry_feature_flags.dart'; +import 'protocol/sentry_feature_flag.dart'; + +/// Integration which handles adding feature flags to the scope. +class FeatureFlagsIntegration extends Integration { + Hub? _hub; + + @override + void call(Hub hub, SentryOptions options) { + _hub = hub; + options.sdk.addIntegration('FeatureFlagsIntegration'); + } + + FutureOr addFeatureFlag(String flag, bool result) async { + final flags = + _hub?.scope.contexts[SentryFeatureFlags.type] as SentryFeatureFlags? ?? + SentryFeatureFlags(values: []); + final values = flags.values; + + if (values.length >= 100) { + values.removeAt(0); + } + + final index = values.indexWhere((element) => element.flag == flag); + if (index != -1) { + values[index] = SentryFeatureFlag(flag: flag, result: result); + } else { + values.add(SentryFeatureFlag(flag: flag, result: result)); + } + + flags.values = values; + + await _hub?.scope.setContexts(SentryFeatureFlags.type, flags); + } + + @override + FutureOr close() { + _hub = null; + } +} diff --git a/packages/dart/lib/src/hint.dart b/packages/dart/lib/src/hint.dart new file mode 100644 index 0000000000..95a22fd0ad --- /dev/null +++ b/packages/dart/lib/src/hint.dart @@ -0,0 +1,120 @@ +import 'protocol/sentry_response.dart'; +import 'sentry_attachment/sentry_attachment.dart'; + +import 'package:meta/meta.dart'; + +/// Hints are used in [BeforeSendCallback], [BeforeBreadcrumbCallback] and +/// event processors. +/// +/// Event and breadcrumb hints are objects containing various information used +/// to put together an event or a breadcrumb. Typically hints hold the original +/// exception so that additional data can be extracted or grouping can be +/// affected. +/// +/// Example: +/// +/// ```dart +/// options.beforeSend = (event, hint) { +/// final syntheticException = hint.get(TypeCheckHint.syntheticException); +/// if (syntheticException is FlutterErrorDetails) { +/// // Do something with hint data +/// } +/// return event; +/// }; +/// } +/// ``` +/// +/// The [Hint] can also be used to add attachments to events. +/// +/// Example: +/// +/// ```dart +/// import 'dart:convert'; +/// +/// options.beforeSend = (event, hint) { +/// final text = 'This event should not be sent happen in prod. Investigate.'; +/// final textAttachment = SentryAttachment.fromIntList( +/// utf8.encode(text), +/// 'event_info.txt', +/// contentType: 'text/plain', +/// ); +/// hint.attachments.add(textAttachment); +/// return event; +/// }; +/// ``` +class Hint { + @internal + static const maxResponseBodySize = 157286; + + final Map _internalStorage = {}; + + final List attachments = []; + + SentryAttachment? screenshot; + + SentryAttachment? viewHierarchy; + + SentryResponse? response; + + Hint(); + + factory Hint.withAttachment(SentryAttachment attachment) { + final hint = Hint(); + hint.attachments.add(attachment); + return hint; + } + + factory Hint.withAttachments(List attachments) { + final hint = Hint(); + hint.attachments.addAll(attachments); + return hint; + } + + factory Hint.withMap(Map map) { + final hint = Hint(); + hint.addAll(map); + return hint; + } + + factory Hint.withScreenshot(SentryAttachment screenshot) { + final hint = Hint(); + hint.screenshot = screenshot; + return hint; + } + + factory Hint.withViewHierarchy(SentryAttachment viewHierarchy) { + final hint = Hint(); + hint.viewHierarchy = viewHierarchy; + return hint; + } + + factory Hint.withResponse(SentryResponse response) { + final hint = Hint(); + hint.response = response; + return hint; + } + + // Key/Value Storage + + void addAll(Map keysAndValues) { + final withoutNullValues = + keysAndValues.map((key, value) => MapEntry(key, value ?? "null")); + _internalStorage.addAll(withoutNullValues); + } + + void set(String key, dynamic value) { + _internalStorage[key] = value ?? "null"; + } + + dynamic get(String key) { + return _internalStorage[key]; + } + + void remove(String key) { + _internalStorage.remove(key); + } + + void clear() { + _internalStorage.clear(); + } +} diff --git a/packages/dart/lib/src/http_client/breadcrumb_client.dart b/packages/dart/lib/src/http_client/breadcrumb_client.dart new file mode 100644 index 0000000000..7f9a1b50d9 --- /dev/null +++ b/packages/dart/lib/src/http_client/breadcrumb_client.dart @@ -0,0 +1,110 @@ +import 'package:http/http.dart'; +import '../protocol.dart'; +import '../hub.dart'; +import '../hub_adapter.dart'; +import '../utils/breadcrumb_log_level.dart'; +import '../utils/url_details.dart'; +import '../utils/http_sanitizer.dart'; + +/// A [http](https://pub.dev/packages/http)-package compatible HTTP client +/// which records requests as breadcrumbs. +/// +/// Remarks: +/// If this client is used as a wrapper, a call to close also closes the +/// given client. +/// +/// The `BreadcrumbClient` can be used as a standalone client like this: +/// ```dart +/// import 'package:sentry/sentry.dart'; +/// +/// var client = BreadcrumbClient(); +/// try { +/// var uriResponse = await client.post('https://example.com/whatsit/create', +/// body: {'name': 'doodle', 'color': 'blue'}); +/// print(await client.get(uriResponse.bodyFields['uri'])); +/// } finally { +/// client.close(); +/// } +/// ``` +/// +/// The `BreadcrumbClient` can also be used as a wrapper for your own +/// HTTP [Client](https://pub.dev/documentation/http/latest/http/Client-class.html): +/// ```dart +/// import 'package:sentry/sentry.dart'; +/// import 'package:http/http.dart' as http; +/// +/// final myClient = http.Client(); +/// +/// var client = BreadcrumbClient(client: myClient); +/// try { +/// var uriResponse = await client.post('https://example.com/whatsit/create', +/// body: {'name': 'doodle', 'color': 'blue'}); +/// print(await client.get(uriResponse.bodyFields['uri'])); +/// } finally { +/// client.close(); +/// } +/// ``` +class BreadcrumbClient extends BaseClient { + BreadcrumbClient({Client? client, Hub? hub}) + : _hub = hub ?? HubAdapter(), + _client = client ?? Client(); + + final Client _client; + final Hub _hub; + + @override + Future send(BaseRequest request) async { + // See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/ + + var requestHadException = false; + int? statusCode; + String? reason; + int? responseBodySize; + + final stopwatch = Stopwatch(); + stopwatch.start(); + + try { + final response = await _client.send(request); + + statusCode = response.statusCode; + reason = response.reasonPhrase; + responseBodySize = response.contentLength; + + return response; + } catch (_) { + requestHadException = true; + rethrow; + } finally { + stopwatch.stop(); + + final urlDetails = + HttpSanitizer.sanitizeUrl(request.url.toString()) ?? UrlDetails(); + + SentryLevel? level; + if (requestHadException) { + level = SentryLevel.error; + } else if (statusCode != null) { + level = getBreadcrumbLogLevelFromHttpStatusCode(statusCode); + } + + var breadcrumb = Breadcrumb.http( + level: level, + url: Uri.parse(urlDetails.urlOrFallback), + method: request.method, + statusCode: statusCode, + reason: reason, + requestDuration: stopwatch.elapsed, + requestBodySize: request.contentLength, + responseBodySize: responseBodySize, + httpQuery: urlDetails.query, + httpFragment: urlDetails.fragment, + ); + + await _hub.addBreadcrumb(breadcrumb); + } + } + + @override + void close() => _client.close(); +} diff --git a/packages/dart/lib/src/http_client/client_provider.dart b/packages/dart/lib/src/http_client/client_provider.dart new file mode 100644 index 0000000000..201a559601 --- /dev/null +++ b/packages/dart/lib/src/http_client/client_provider.dart @@ -0,0 +1,16 @@ +import 'package:meta/meta.dart'; +import 'package:http/http.dart'; + +import '../sentry_options.dart'; + +@internal +ClientProvider getClientProvider() { + return ClientProvider(); +} + +@internal +class ClientProvider { + Client getClient(SentryOptions options) { + return Client(); + } +} diff --git a/packages/dart/lib/src/http_client/failed_request_client.dart b/packages/dart/lib/src/http_client/failed_request_client.dart new file mode 100644 index 0000000000..2e494e02bb --- /dev/null +++ b/packages/dart/lib/src/http_client/failed_request_client.dart @@ -0,0 +1,254 @@ +import 'package:http/http.dart'; + +import '../hint.dart'; +import '../hub.dart'; +import '../hub_adapter.dart'; +import '../protocol.dart'; +import '../throwable_mechanism.dart'; +import '../type_check_hint.dart'; +import '../utils/tracing_utils.dart'; +import 'sentry_http_client.dart'; +import 'sentry_http_client_error.dart'; + +/// A [http](https://pub.dev/packages/http)-package compatible HTTP client +/// which records events for failed requests. +/// +/// Configured with default values, this captures requests which throw an +/// exception. +/// This can be for example for the following reasons: +/// - In an browser environment this can be requests which fail because of CORS. +/// - In an mobile or desktop application this can be requests which failed +/// because the connection was interrupted. +/// +/// Additionally you can configure specific HTTP response codes to be considered +/// as a failed request. In the following example, the status codes 404 and 500 +/// are considered a failed request. +/// +/// ```dart +/// import 'package:sentry/sentry.dart'; +/// +/// var client = FailedRequestClient( +/// failedRequestStatusCodes: [SentryStatusCode.range(400, 404), SentryStatusCode(500)] +/// ); +/// ``` +/// +/// Remarks: +/// If this client is used as a wrapper, a call to close also closes the +/// given client. +/// +/// The `FailedRequestClient` can be used as a standalone client like this: +/// ```dart +/// import 'package:sentry/sentry.dart'; +/// +/// var client = FailedRequestClient(); +/// try { +/// var uriResponse = await client.post('https://example.com/whatsit/create', +/// body: {'name': 'doodle', 'color': 'blue'}); +/// print(await client.get(uriResponse.bodyFields['uri'])); +/// } finally { +/// client.close(); +/// } +/// ``` +/// +/// The `FailedRequestClient` can also be used as a wrapper for your own +/// HTTP [Client](https://pub.dev/documentation/http/latest/http/Client-class.html): +/// ```dart +/// import 'package:sentry/sentry.dart'; +/// import 'package:http/http.dart' as http; +/// +/// final myClient = http.Client(); +/// +/// var client = FailedRequestClient(client: myClient); +/// try { +/// var uriResponse = await client.post('https://example.com/whatsit/create', +/// body: {'name': 'doodle', 'color': 'blue'}); +/// print(await client.get(uriResponse.bodyFields['uri'])); +/// } finally { +/// client.close(); +/// } +/// ``` +class FailedRequestClient extends BaseClient { + FailedRequestClient({ + this.failedRequestStatusCodes = + SentryHttpClient.defaultFailedRequestStatusCodes, + this.failedRequestTargets = SentryHttpClient.defaultFailedRequestTargets, + Client? client, + Hub? hub, + bool? captureFailedRequests, + }) : _hub = hub ?? HubAdapter(), + _client = client ?? Client(), + _captureFailedRequests = captureFailedRequests { + if (captureFailedRequests ?? _hub.options.captureFailedRequests) { + _hub.options.sdk.addIntegration('HTTPClientError'); + } + } + + final Client _client; + final Hub _hub; + final bool? _captureFailedRequests; + + /// Describes which HTTP status codes should be considered as a failed + /// requests. + /// + /// Per default no status code is considered a failed request. + final List failedRequestStatusCodes; + + final List failedRequestTargets; + + @override + Future send(BaseRequest request) async { + int? statusCode; + Object? exception; + StackTrace? stackTrace; + StreamedResponse? response; + + final stopwatch = Stopwatch(); + stopwatch.start(); + + try { + response = await _client.send(request); + statusCode = response.statusCode; + return response; + } catch (e, st) { + exception = e; + stackTrace = st; + rethrow; + } finally { + stopwatch.stop(); + await _captureEventIfNeeded( + request, + statusCode, + exception, + stackTrace, + response, + stopwatch.elapsed, + ); + } + } + + Future _captureEventIfNeeded( + BaseRequest request, + int? statusCode, + Object? exception, + StackTrace? stackTrace, + StreamedResponse? response, + Duration duration) async { + if (!(_captureFailedRequests ?? _hub.options.captureFailedRequests)) { + return; + } + + // Only check `failedRequestStatusCodes` & `failedRequestTargets` if no exception was thrown. + if (exception == null) { + if (!failedRequestStatusCodes._containsStatusCode(statusCode)) { + return; + } + if (!containsTargetOrMatchesRegExp( + failedRequestTargets, request.url.toString())) { + return; + } + } + + final reason = 'HTTP Client Error with status code: $statusCode'; + exception ??= SentryHttpClientError(reason); + + await _captureEvent( + exception: exception, + stackTrace: stackTrace, + request: request, + requestDuration: duration, + response: response, + reason: reason, + ); + } + + @override + void close() => _client.close(); + + // See https://develop.sentry.dev/sdk/event-payloads/request/ + Future _captureEvent({ + required Object? exception, + StackTrace? stackTrace, + String? reason, + required Duration requestDuration, + required BaseRequest request, + required StreamedResponse? response, + }) async { + final sentryRequest = SentryRequest.fromUri( + method: request.method, + headers: _hub.options.sendDefaultPii ? request.headers : null, + uri: request.url, + data: _hub.options.sendDefaultPii ? _getDataFromRequest(request) : null, + ); + + final mechanism = Mechanism( + type: 'SentryHttpClient', + description: reason, + ); + + bool? snapshot; + if (exception is SentryHttpClientError) { + snapshot = true; + } + + final throwableMechanism = ThrowableMechanism( + mechanism, + exception, + snapshot: snapshot, + ); + + final event = SentryEvent( + throwable: throwableMechanism, + request: sentryRequest, + timestamp: _hub.options.clock(), + ); + + final hint = Hint.withMap({TypeCheckHint.httpRequest: request}); + + if (response != null) { + event.contexts.response = SentryResponse( + headers: _hub.options.sendDefaultPii ? response.headers : null, + bodySize: response.contentLength, + statusCode: response.statusCode, + ); + hint.set(TypeCheckHint.httpResponse, response); + } + + await _hub.captureEvent( + event, + stackTrace: stackTrace, + hint: hint, + ); + } + + // Types of Request can be found here: + // https://pub.dev/documentation/http/latest/http/http-library.html + Object? _getDataFromRequest(BaseRequest request) { + final contentLength = request.contentLength; + if (contentLength == null) { + return null; + } + if (!_hub.options.maxRequestBodySize.shouldAddBody(contentLength)) { + return null; + } + if (request is MultipartRequest) { + final data = {...request.fields}; + return data; + } + + if (request is Request) { + return request.body; + } + + // There's nothing we can do for a StreamedRequest + return null; + } +} + +extension _ListX on List { + bool _containsStatusCode(int? statusCode) { + if (statusCode == null) { + return false; + } + return any((element) => element.isInRange(statusCode)); + } +} diff --git a/packages/dart/lib/src/http_client/io_client_provider.dart b/packages/dart/lib/src/http_client/io_client_provider.dart new file mode 100644 index 0000000000..6e611cbb97 --- /dev/null +++ b/packages/dart/lib/src/http_client/io_client_provider.dart @@ -0,0 +1,67 @@ +import 'dart:io'; + +import 'package:http/http.dart'; +import 'package:http/io_client.dart'; +import 'package:meta/meta.dart'; + +import '../protocol.dart'; +import '../protocol/sentry_proxy.dart'; +import '../sentry_options.dart'; +import 'client_provider.dart'; + +@internal +ClientProvider getClientProvider() { + return IoClientProvider( + () { + return HttpClient(); + }, + (user, pass) { + return HttpClientBasicCredentials(user, pass); + }, + ); +} + +@internal +class IoClientProvider implements ClientProvider { + final HttpClient Function() _httpClient; + final HttpClientCredentials Function(String, String) _httpClientCredentials; + + IoClientProvider(this._httpClient, this._httpClientCredentials); + + @override + Client getClient(SentryOptions options) { + final proxy = options.proxy; + if (proxy == null) { + return Client(); + } + final pac = proxy.toPacString(); + if (proxy.type == SentryProxyType.socks) { + options.log( + SentryLevel.warning, + "Setting proxy '$pac' is not supported.", + ); + return Client(); + } + options.log( + SentryLevel.info, + "Setting proxy '$pac'", + ); + final httpClient = _httpClient(); + httpClient.findProxy = (url) => pac; + + final host = proxy.host; + final port = proxy.port; + final user = proxy.user; + final pass = proxy.pass; + + if (host != null && port != null && user != null && pass != null) { + httpClient.addProxyCredentials( + host, + port, + '', + _httpClientCredentials(user, pass), + ); + } + return IOClient(httpClient); + } +} diff --git a/packages/dart/lib/src/http_client/sentry_http_client.dart b/packages/dart/lib/src/http_client/sentry_http_client.dart new file mode 100644 index 0000000000..b6933a07a1 --- /dev/null +++ b/packages/dart/lib/src/http_client/sentry_http_client.dart @@ -0,0 +1,160 @@ +import 'package:http/http.dart'; + +import '../hub.dart'; +import '../hub_adapter.dart'; +import 'breadcrumb_client.dart'; +import 'failed_request_client.dart'; +import 'tracing_client.dart'; + +/// A [http](https://pub.dev/packages/http)-package compatible HTTP client. +/// +/// Additionally you can configure specific HTTP response codes to be considered +/// as a failed request. This is off by default. Enable it by using it like +/// shown in the following example: +/// The status codes 400 to 404 and 500 are considered a failed request. +/// +/// ```dart +/// import 'package:sentry/sentry.dart'; +/// +/// var client = SentryHttpClient( +/// failedRequestStatusCodes: [ +/// SentryStatusCode.range(400, 404), +/// SentryStatusCode(500), +/// ], +/// ); +/// ``` +/// +/// If empty request status codes are provided, all failure requests will be +/// captured. Per default, codes in the range 500-599 are recorded. +/// +/// If you provide failed request targets, the SDK will only capture HTTP +/// Client errors if the HTTP Request URL is a match for any of the provided +/// targets. +/// +/// ```dart +/// var client = SentryHttpClient( +/// failedRequestTargets: ['my-api.com'], +/// ); +/// ``` +/// +/// Remarks: If this client is used as a wrapper, a call to close also closes +/// the given client. +/// +/// The `SentryHttpClient` can be used as a standalone client like this: +/// ```dart +/// import 'package:sentry/sentry.dart'; +/// +/// var client = SentryHttpClient(); +/// try { +/// var uriResponse = await client.post('https://example.com/whatsit/create', +/// body: {'name': 'doodle', 'color': 'blue'}); +/// print(await client.get(uriResponse.bodyFields['uri'])); +/// } finally { +/// client.close(); +/// } +/// ``` +/// +/// The `SentryHttpClient` can also be used as a wrapper for your own HTTP +/// [Client](https://pub.dev/documentation/http/latest/http/Client-class.html): +/// ```dart +/// import 'package:sentry/sentry.dart'; +/// import 'package:http/http.dart' as http; +/// +/// final myClient = http.Client(); +/// +/// var client = SentryHttpClient(client: myClient); +/// try { +/// var uriResponse = await client.post('https://example.com/whatsit/create', +/// body: {'name': 'doodle', 'color': 'blue'}); +/// print(await client.get(uriResponse.bodyFields['uri'])); +/// } finally { +/// client.close(); +/// } +/// +/// Remarks: +/// HTTP traffic can contain PII (personal identifiable information). +/// Read more on data scrubbing [here](https://docs.sentry.io/product/data-management-settings/advanced-datascrubbing/). +/// +/// The constructor parameter `captureFailedRequests` will override what you +/// have configured in options. +/// ``` +class SentryHttpClient extends BaseClient { + static const defaultFailedRequestStatusCodes = [ + SentryStatusCode.defaultRange() + ]; + static const defaultFailedRequestTargets = ['.*']; + + SentryHttpClient({ + Client? client, + Hub? hub, + List failedRequestStatusCodes = + defaultFailedRequestStatusCodes, + List failedRequestTargets = defaultFailedRequestTargets, + bool? captureFailedRequests, + }) { + _hub = hub ?? HubAdapter(); + + var innerClient = client ?? Client(); + + innerClient = FailedRequestClient( + failedRequestStatusCodes: failedRequestStatusCodes, + failedRequestTargets: failedRequestTargets, + hub: _hub, + client: innerClient, + captureFailedRequests: captureFailedRequests, + ); + + innerClient = TracingClient(client: innerClient, hub: _hub); + + // The ordering here matters. + // We don't want to include the breadcrumbs for the current request + // when capturing it as a failed request. + // However it still should be added for following events. + if (_hub.options.recordHttpBreadcrumbs) { + innerClient = BreadcrumbClient(client: innerClient, hub: _hub); + } + + _client = innerClient; + } + + late Client _client; + late Hub _hub; + + @override + Future send(BaseRequest request) => _client.send(request); + + // See https://github.com/getsentry/sentry-dart/pull/226#discussion_r536984785 + @override + void close() => _client.close(); +} + +class SentryStatusCode { + static const _defaultMin = 500; + static const _defaultMax = 599; + + const SentryStatusCode.defaultRange() + : _min = _defaultMin, + _max = _defaultMax; + + SentryStatusCode.range(this._min, this._max) + : assert(_min <= _max), + assert(_min > 0 && _max > 0); + + SentryStatusCode(int statusCode) + : _min = statusCode, + _max = statusCode, + assert(statusCode > 0); + + final int _min; + final int _max; + + bool isInRange(int statusCode) => statusCode >= _min && statusCode <= _max; + + @override + String toString() { + if (_min == _max) { + return _min.toString(); + } + return '$_min..$_max'; + } +} diff --git a/packages/dart/lib/src/http_client/sentry_http_client_error.dart b/packages/dart/lib/src/http_client/sentry_http_client_error.dart new file mode 100644 index 0000000000..60e1abdfe3 --- /dev/null +++ b/packages/dart/lib/src/http_client/sentry_http_client_error.dart @@ -0,0 +1,7 @@ +class SentryHttpClientError implements Exception { + final String _message; + SentryHttpClientError(this._message); + + @override + String toString() => 'Exception: $_message'; +} diff --git a/packages/dart/lib/src/http_client/tracing_client.dart b/packages/dart/lib/src/http_client/tracing_client.dart new file mode 100644 index 0000000000..8086d13a16 --- /dev/null +++ b/packages/dart/lib/src/http_client/tracing_client.dart @@ -0,0 +1,80 @@ +import 'package:http/http.dart'; + +import '../hub.dart'; +import '../hub_adapter.dart'; +import '../protocol.dart'; +import '../sentry_trace_origins.dart'; +import '../tracing.dart'; +import '../utils/http_sanitizer.dart'; +import '../utils/tracing_utils.dart'; + +/// A [http](https://pub.dev/packages/http)-package compatible HTTP client +/// which adds support to Sentry Performance feature. If tracing is disabled +/// generated spans will be no-op. This client also handles adding the +/// Sentry trace headers to the HTTP request header. +/// https://develop.sentry.dev/sdk/performance +class TracingClient extends BaseClient { + static const String integrationName = 'HTTPNetworkTracing'; + + TracingClient({Client? client, Hub? hub}) + : _hub = hub ?? HubAdapter(), + _client = client ?? Client() { + if (_hub.options.isTracingEnabled()) { + _hub.options.sdk.addIntegration(integrationName); + } + } + + final Client _client; + final Hub _hub; + + @override + Future send(BaseRequest request) async { + // see https://develop.sentry.dev/sdk/performance/#header-sentry-trace + final urlDetails = HttpSanitizer.sanitizeUrl(request.url.toString()); + + var description = request.method; + if (urlDetails != null) { + description += ' ${urlDetails.urlOrFallback}'; + } + + final currentSpan = _hub.getSpan(); + var span = currentSpan?.startChild( + 'http.client', + description: description, + ); + + if (span is NoOpSentrySpan) { + span = null; + } + + // Regardless whether tracing is enabled or not, we always want to attach + // Sentry trace headers (tracing without performance). + if (containsTargetOrMatchesRegExp( + _hub.options.tracePropagationTargets, request.url.toString())) { + addTracingHeadersToHttpHeader(request.headers, _hub, span: span); + } + + span?.origin = SentryTraceOrigins.autoHttpHttp; + span?.setData('http.request.method', request.method); + urlDetails?.applyToSpan(span); + + StreamedResponse? response; + try { + response = await _client.send(request); + span?.setData('http.response.status_code', response.statusCode); + span?.setData('http.response_content_length', response.contentLength); + span?.status = SpanStatus.fromHttpStatusCode(response.statusCode); + } catch (exception) { + span?.throwable = exception; + span?.status = SpanStatus.internalError(); + + rethrow; + } finally { + await span?.finish(); + } + return response; + } + + @override + void close() => _client.close(); +} diff --git a/packages/dart/lib/src/hub.dart b/packages/dart/lib/src/hub.dart new file mode 100644 index 0000000000..a4533793c7 --- /dev/null +++ b/packages/dart/lib/src/hub.dart @@ -0,0 +1,784 @@ +import 'dart:async'; +import 'dart:collection'; +import 'dart:math'; + +import 'package:meta/meta.dart'; + +import '../sentry.dart'; +import 'client_reports/discard_reason.dart'; +import 'profiling.dart'; +import 'sentry_tracer.dart'; +import 'sentry_traces_sampler.dart'; +import 'transport/data_category.dart'; + +/// Configures the scope through the callback. +typedef ScopeCallback = FutureOr Function(Scope); + +/// Called when a transaction is finished. +typedef OnTransactionFinish = FutureOr Function(ISentrySpan transaction); + +/// SDK API contract which combines a client and scope management +class Hub { + static SentryClient _getClient(SentryOptions options) { + return SentryClient(options); + } + + final ListQueue<_StackItem> _stack = ListQueue(); + + // peek can never return null since Stack can be created only with an item and + // pop does not drop the last item. + _StackItem _peek() => _stack.first; + + final SentryOptions _options; + + @internal + SentryOptions get options => _options; + + late SentryTracesSampler _tracesSampler; + + late final _WeakMap _throwableToSpan; + + factory Hub(SentryOptions options) { + _validateOptions(options); + + return Hub._(options); + } + + Hub._(this._options) { + _tracesSampler = SentryTracesSampler(_options); + _stack.add(_StackItem(_getClient(_options), Scope(_options))); + _isEnabled = true; + _throwableToSpan = _WeakMap(_options); + } + + static void _validateOptions(SentryOptions options) { + if (options.dsn == null) { + throw ArgumentError('DSN is required.'); + } + } + + bool _isEnabled = false; + + /// Check if the Hub is enabled/active. + bool get isEnabled => _isEnabled; + + SentryId _lastEventId = SentryId.empty(); + + /// Last event id recorded by the Hub + SentryId get lastEventId => _lastEventId; + + @internal + Scope get scope => _peek().scope; + + /// Captures the event. + Future captureEvent( + SentryEvent event, { + dynamic stackTrace, + Hint? hint, + ScopeCallback? withScope, + }) async { + var sentryId = SentryId.empty(); + + if (!_isEnabled) { + _options.log( + SentryLevel.warning, + "Instance is disabled and this 'captureEvent' call is a no-op.", + ); + } else { + final item = _peek(); + late Scope scope; + final s = _cloneAndRunWithScope(item.scope, withScope); + if (s is Future) { + scope = await s; + } else { + scope = s; + } + + try { + if (_options.isTracingEnabled()) { + event = _assignTraceContext(event); + } + + sentryId = await item.client.captureEvent( + event, + stackTrace: stackTrace, + scope: scope, + hint: hint, + ); + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'Error while capturing event with id: ${event.eventId}', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } finally { + _lastEventId = sentryId; + } + } + return sentryId; + } + + /// Captures the exception + Future captureException( + dynamic throwable, { + dynamic stackTrace, + Hint? hint, + SentryMessage? message, + ScopeCallback? withScope, + }) async { + var sentryId = SentryId.empty(); + + if (!_isEnabled) { + _options.log( + SentryLevel.warning, + "Instance is disabled and this 'captureException' call is a no-op.", + ); + } else if (throwable == null) { + _options.log( + SentryLevel.warning, + 'captureException called with null parameter.', + ); + } else { + final item = _peek(); + late Scope scope; + final s = _cloneAndRunWithScope(item.scope, withScope); + if (s is Future) { + scope = await s; + } else { + scope = s; + } + + try { + var event = SentryEvent( + throwable: throwable, + timestamp: _options.clock(), + message: message, + ); + + if (_options.isTracingEnabled()) { + event = _assignTraceContext(event); + } + + sentryId = await item.client.captureEvent( + event, + stackTrace: stackTrace, + scope: scope, + hint: hint, + ); + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'Error while capturing exception', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } finally { + _lastEventId = sentryId; + } + } + + return sentryId; + } + + /// Captures the message. + Future captureMessage( + String? message, { + SentryLevel? level, + String? template, + List? params, + Hint? hint, + ScopeCallback? withScope, + }) async { + var sentryId = SentryId.empty(); + + if (!_isEnabled) { + _options.log( + SentryLevel.warning, + "Instance is disabled and this 'captureMessage' call is a no-op.", + ); + } else if (message == null) { + _options.log( + SentryLevel.warning, + 'captureMessage called with null parameter.', + ); + } else { + final item = _peek(); + late Scope scope; + final s = _cloneAndRunWithScope(item.scope, withScope); + if (s is Future) { + scope = await s; + } else { + scope = s; + } + + try { + sentryId = await item.client.captureMessage( + message, + level: level, + template: template, + params: params, + scope: scope, + hint: hint, + ); + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'Error while capturing message with id: $message', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } finally { + _lastEventId = sentryId; + } + } + return sentryId; + } + + /// Captures the feedback. + Future captureFeedback( + SentryFeedback feedback, { + Hint? hint, + ScopeCallback? withScope, + }) async { + var sentryId = SentryId.empty(); + + if (!_isEnabled) { + _options.log( + SentryLevel.warning, + "Instance is disabled and this 'captureFeedback' call is a no-op.", + ); + } else { + final item = _peek(); + late Scope scope; + final s = _cloneAndRunWithScope(item.scope, withScope); + if (s is Future) { + scope = await s; + } else { + scope = s; + } + + try { + sentryId = await item.client.captureFeedback( + feedback, + hint: hint, + scope: scope, + ); + } catch (exception, stacktrace) { + _options.log( + SentryLevel.error, + 'Error while capturing feedback', + exception: exception, + stackTrace: stacktrace, + ); + } + } + return sentryId; + } + + FutureOr captureLog(SentryLog log) async { + if (!_isEnabled) { + _options.log( + SentryLevel.warning, + "Instance is disabled and this 'captureFeedback' call is a no-op.", + ); + } else { + final item = _peek(); + late Scope scope; + final s = _cloneAndRunWithScope(item.scope, null); + if (s is Future) { + scope = await s; + } else { + scope = s; + } + + try { + await item.client.captureLog( + log, + scope: scope, + ); + } catch (exception, stacktrace) { + _options.log( + SentryLevel.error, + 'Error while capturing log', + exception: exception, + stackTrace: stacktrace, + ); + } + } + } + + FutureOr _cloneAndRunWithScope( + Scope scope, ScopeCallback? withScope) async { + if (withScope != null) { + try { + scope = scope.clone(); + final s = withScope(scope); + if (s is Future) { + await s; + } + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'Exception in withScope callback.', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } + } + return scope; + } + + /// Adds a breacrumb to the current Scope + Future addBreadcrumb(Breadcrumb crumb, {Hint? hint}) async { + if (!_isEnabled) { + _options.log( + SentryLevel.warning, + "Instance is disabled and this 'addBreadcrumb' call is a no-op.", + ); + } else { + final item = _peek(); + await item.scope.addBreadcrumb(crumb, hint: hint); + } + } + + /// Binds a different client to the hub + void bindClient(SentryClient client) { + if (!_isEnabled) { + _options.log( + SentryLevel.warning, + "Instance is disabled and this 'bindClient' call is a no-op.", + ); + } else { + final item = _peek(); + _options.log(SentryLevel.debug, 'New client bound to scope.'); + item.client = client; + } + } + + /// Clones the Hub + Hub clone() { + if (!_isEnabled) { + _options.log(SentryLevel.warning, 'Disabled Hub cloned.'); + } + final clone = Hub(_options); + for (final item in _stack) { + clone._stack.add(_StackItem(item.client, item.scope.clone())); + } + return clone; + } + + /// Flushes out the queue for up to timeout seconds and disable the Hub. + Future close() async { + if (!_isEnabled) { + _options.log( + SentryLevel.warning, + "Instance is disabled and this 'close' call is a no-op.", + ); + } else { + // close integrations + for (final integration in _options.integrations) { + final close = integration.close(); + if (close is Future) { + await close; + } + } + + final item = _peek(); + + try { + item.client.close(); + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'Error while closing the Hub', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } + + _isEnabled = false; + } + } + + /// Configures the scope through the callback. + FutureOr configureScope(ScopeCallback callback) async { + if (!_isEnabled) { + _options.log( + SentryLevel.warning, + "Instance is disabled and this 'configureScope' call is a no-op.", + ); + } else { + final item = _peek(); + + try { + final result = callback(item.scope); + if (result is Future) { + await result; + } + } catch (err) { + _options.log( + SentryLevel.error, + "Error in the 'configureScope' callback, error: $err", + ); + if (_options.automatedTestMode) { + rethrow; + } + } + } + } + + /// Creates a Transaction and returns the instance. + ISentrySpan startTransaction( + String name, + String operation, { + String? description, + DateTime? startTimestamp, + bool? bindToScope, + bool? waitForChildren, + Duration? autoFinishAfter, + bool? trimEnd, + OnTransactionFinish? onFinish, + Map? customSamplingContext, + }) => + startTransactionWithContext( + SentryTransactionContext( + name, + operation, + description: description, + origin: SentryTraceOrigins.manual, + ), + startTimestamp: startTimestamp, + bindToScope: bindToScope, + waitForChildren: waitForChildren, + autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, + onFinish: onFinish, + customSamplingContext: customSamplingContext, + ); + + /// Creates a Transaction and returns the instance. + ISentrySpan startTransactionWithContext( + SentryTransactionContext transactionContext, { + Map? customSamplingContext, + DateTime? startTimestamp, + bool? bindToScope, + bool? waitForChildren, + Duration? autoFinishAfter, + bool? trimEnd, + OnTransactionFinish? onFinish, + }) { + if (!_isEnabled) { + _options.log( + SentryLevel.warning, + "Instance is disabled and this 'startTransaction' call is a no-op.", + ); + } else if (_options.isTracingEnabled()) { + final item = _peek(); + + // if transactionContext has no sampling decision yet, run the traces sampler + var samplingDecision = transactionContext.samplingDecision; + final propagationContext = scope.propagationContext; + // Store the generated/used sampleRand on the propagation context so + // that subsequent transactions in the same trace reuse it. + propagationContext.sampleRand ??= Random().nextDouble(); + + if (samplingDecision == null) { + final samplingContext = SentrySamplingContext( + transactionContext, customSamplingContext ?? {}); + + samplingDecision = _tracesSampler.sample( + samplingContext, + // sampleRand is guaranteed not to be null here + propagationContext.sampleRand!, + ); + + // Persist the sampling decision within the transaction context + transactionContext.samplingDecision = samplingDecision; + } + + transactionContext.origin ??= SentryTraceOrigins.manual; + transactionContext.traceId = propagationContext.traceId; + + // Persist the "sampled" decision onto the propagation context the + // first time we obtain one for the current trace. + // Subsequent transactions do not affect the sampled flag. + propagationContext.applySamplingDecision(samplingDecision.sampled); + + SentryProfiler? profiler; + if (_profilerFactory != null && + _tracesSampler.sampleProfiling(samplingDecision)) { + profiler = _profilerFactory?.startProfiler(transactionContext); + } + + final tracer = SentryTracer( + transactionContext, + this, + startTimestamp: startTimestamp, + waitForChildren: waitForChildren ?? false, + autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd ?? false, + onFinish: onFinish, + profiler: profiler, + ); + if (bindToScope ?? false) { + item.scope.span = tracer; + } + + return tracer; + } + + return NoOpSentrySpan(); + } + + @internal + void generateNewTrace() { + // Create a brand-new trace and reset the sampling flag and sampleRand so + // that the next root transaction can set it again. + scope.propagationContext.resetTrace(); + } + + /// Gets the current active transaction or span. + ISentrySpan? getSpan() { + ISentrySpan? span; + if (!_isEnabled) { + _options.log( + SentryLevel.warning, + "Instance is disabled and this 'getSpan' call is a no-op.", + ); + } else if (_options.isTracingEnabled()) { + final item = _peek(); + + span = item.scope.span; + } + + return span; + } + + @internal + Future captureTransaction( + SentryTransaction transaction, { + SentryTraceContextHeader? traceContext, + Hint? hint, + }) async { + var sentryId = SentryId.empty(); + + if (!_isEnabled) { + _options.log( + SentryLevel.warning, + "Instance is disabled and this 'captureTransaction' call is a no-op.", + ); + } else if (!_options.isTracingEnabled()) { + _options.log( + SentryLevel.info, + "Tracing is disabled and this 'captureTransaction' call is a no-op.", + ); + } else if (!transaction.finished) { + _options.log( + SentryLevel.warning, + 'Capturing unfinished transaction: ${transaction.eventId}', + ); + } else { + final item = _peek(); + + if (!transaction.sampled) { + _options.recorder.recordLostEvent( + DiscardReason.sampleRate, + DataCategory.transaction, + ); + _options.recorder.recordLostEvent( + DiscardReason.sampleRate, + DataCategory.span, + count: transaction.spans.length + 1, + ); + _options.log( + SentryLevel.warning, + 'Transaction ${transaction.eventId} was dropped due to sampling decision.', + ); + } else { + try { + sentryId = await item.client.captureTransaction( + transaction, + scope: item.scope, + traceContext: traceContext, + hint: hint, + ); + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'Error while capturing transaction with id: ${transaction.eventId}', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } + } + } + return sentryId; + } + + @internal + void setSpanContext( + dynamic throwable, + ISentrySpan span, + String transaction, + ) => + _throwableToSpan.add(throwable, span, transaction); + + @internal + SentryProfilerFactory? get profilerFactory => _profilerFactory; + + @internal + set profilerFactory(SentryProfilerFactory? value) => _profilerFactory = value; + + SentryProfilerFactory? _profilerFactory; + + @internal + Map> get lifecycleCallbacks => + _peek().client.lifeCycleRegistry.lifecycleCallbacks; + + @internal + void registerSdkLifecycleCallback( + SdkLifecycleCallback callback) { + _peek().client.lifeCycleRegistry.registerCallback(callback); + } + + @internal + void removeSdkLifecycleCallback( + SdkLifecycleCallback callback) { + _peek().client.lifeCycleRegistry.removeCallback(callback); + } + + SentryEvent _assignTraceContext(SentryEvent event) { + // assign trace context + if (event.throwable != null && event.contexts.trace == null) { + // set span to event.contexts.trace + final pair = _throwableToSpan.get(event.throwable); + if (pair != null) { + final span = pair.key; + final spanContext = span.context; + event.contexts.trace = spanContext.toTraceContext( + sampled: span.samplingDecision?.sampled, + ); + + // set transaction name to event.transaction + event.transaction ??= pair.value; + } + } + return event; + } +} + +class _StackItem { + SentryClient client; + + final Scope scope; + + _StackItem(this.client, this.scope); +} + +class _WeakMap { + final _expando = Expando(); + + final SentryOptions _options; + + final throwableHandler = UnsupportedThrowablesHandler(); + + _WeakMap(this._options); + + void add( + dynamic throwable, + ISentrySpan span, + String transaction, + ) { + if (throwable == null) { + return; + } + throwable = throwableHandler.wrapIfUnsupportedType(throwable); + try { + if (_expando[throwable] == null) { + _expando[throwable] = MapEntry(span, transaction); + } + } catch (exception, stackTrace) { + _options.log( + SentryLevel.info, + 'Throwable type: ${throwable.runtimeType} is not supported for associating errors to a transaction.', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } + } + + MapEntry? get(dynamic throwable) { + if (throwable == null) { + return null; + } + throwable = throwableHandler.wrapIfUnsupportedType(throwable); + try { + return _expando[throwable] as MapEntry?; + } catch (exception, stackTrace) { + _options.log( + SentryLevel.info, + 'Throwable type: ${throwable.runtimeType} is not supported for associating errors to a transaction.', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } + return null; + } +} + +/// A handler for unsupported throwables used for Expando. +@visibleForTesting +class UnsupportedThrowablesHandler { + final _unsupportedTypes = {String, int, double, bool}; + final _unsupportedThrowables = {}; + + dynamic wrapIfUnsupportedType(dynamic throwable) { + if (_unsupportedTypes.contains(throwable.runtimeType)) { + throwable = _UnsupportedExceptionWrapper(Exception(throwable)); + _unsupportedThrowables.add(throwable); + } + return _unsupportedThrowables.lookup(throwable) ?? throwable; + } +} + +class _UnsupportedExceptionWrapper { + _UnsupportedExceptionWrapper(this.exception); + + final Exception exception; + + @override + bool operator ==(Object other) { + if (other is _UnsupportedExceptionWrapper) { + return other.exception.toString() == exception.toString(); + } + return false; + } + + @override + int get hashCode => exception.toString().hashCode; +} diff --git a/packages/dart/lib/src/hub_adapter.dart b/packages/dart/lib/src/hub_adapter.dart new file mode 100644 index 0000000000..7595954444 --- /dev/null +++ b/packages/dart/lib/src/hub_adapter.dart @@ -0,0 +1,219 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; + +import 'hint.dart'; +import 'hub.dart'; +import 'profiling.dart'; +import 'protocol.dart'; +import 'protocol/sentry_feedback.dart'; +import 'scope.dart'; +import 'sdk_lifecycle_hooks.dart'; +import 'sentry.dart'; +import 'sentry_client.dart'; +import 'sentry_options.dart'; +import 'tracing.dart'; + +/// Hub adapter to make Integrations testable +class HubAdapter implements Hub { + const HubAdapter._(); + + static final HubAdapter _instance = HubAdapter._(); + + @override + @internal + SentryOptions get options => Sentry.currentHub.options; + + factory HubAdapter() { + return _instance; + } + + @override + Future addBreadcrumb(Breadcrumb crumb, {Hint? hint}) async => + await Sentry.addBreadcrumb(crumb, hint: hint); + + @override + void bindClient(SentryClient client) => Sentry.bindClient(client); + + @override + Future captureEvent( + SentryEvent event, { + dynamic stackTrace, + Hint? hint, + ScopeCallback? withScope, + }) => + Sentry.captureEvent( + event, + stackTrace: stackTrace, + hint: hint, + withScope: withScope, + ); + + @override + Future captureException( + dynamic throwable, { + dynamic stackTrace, + Hint? hint, + SentryMessage? message, + ScopeCallback? withScope, + }) => + Sentry.captureException( + throwable, + stackTrace: stackTrace, + hint: hint, + message: message, + withScope: withScope, + ); + + @override + Future captureMessage( + String? message, { + SentryLevel? level, + String? template, + List? params, + Hint? hint, + ScopeCallback? withScope, + }) => + Sentry.captureMessage( + message, + level: level, + template: template, + params: params, + hint: hint, + withScope: withScope, + ); + + @override + Hub clone() => Sentry.clone(); + + @override + Future close() => Sentry.close(); + + @override + FutureOr configureScope(ScopeCallback callback) => + Sentry.configureScope(callback); + + @override + bool get isEnabled => Sentry.isEnabled; + + @override + SentryId get lastEventId => Sentry.lastEventId; + + @override + Future captureTransaction( + SentryTransaction transaction, { + SentryTraceContextHeader? traceContext, + Hint? hint, + }) => + Sentry.currentHub.captureTransaction( + transaction, + traceContext: traceContext, + hint: hint, + ); + + @override + ISentrySpan? getSpan() => Sentry.currentHub.getSpan(); + + @override + ISentrySpan startTransactionWithContext( + SentryTransactionContext transactionContext, { + Map? customSamplingContext, + DateTime? startTimestamp, + bool? bindToScope, + bool? waitForChildren, + Duration? autoFinishAfter, + bool? trimEnd, + OnTransactionFinish? onFinish, + }) => + Sentry.startTransactionWithContext( + transactionContext, + customSamplingContext: customSamplingContext, + startTimestamp: startTimestamp, + bindToScope: bindToScope, + waitForChildren: waitForChildren, + autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, + onFinish: onFinish, + ); + + @override + ISentrySpan startTransaction( + String name, + String operation, { + String? description, + DateTime? startTimestamp, + bool? bindToScope, + bool? waitForChildren, + Duration? autoFinishAfter, + bool? trimEnd, + OnTransactionFinish? onFinish, + Map? customSamplingContext, + }) => + Sentry.startTransaction( + name, + operation, + description: description, + startTimestamp: startTimestamp, + bindToScope: bindToScope, + waitForChildren: waitForChildren, + autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, + onFinish: onFinish, + customSamplingContext: customSamplingContext, + ); + + @override + void generateNewTrace() => Sentry.currentHub.generateNewTrace(); + + @override + void setSpanContext( + dynamic throwable, + ISentrySpan span, + String transaction, + ) => + Sentry.currentHub.setSpanContext(throwable, span, transaction); + + @internal + @override + set profilerFactory(SentryProfilerFactory? value) => + Sentry.currentHub.profilerFactory = value; + + @internal + @override + SentryProfilerFactory? get profilerFactory => + Sentry.currentHub.profilerFactory; + + @override + Scope get scope => Sentry.currentHub.scope; + + @override + Future captureFeedback( + SentryFeedback feedback, { + Hint? hint, + ScopeCallback? withScope, + }) => + Sentry.currentHub.captureFeedback( + feedback, + hint: hint, + withScope: withScope, + ); + + @override + FutureOr captureLog(SentryLog log) => Sentry.currentHub.captureLog(log); + + @override + Map> get lifecycleCallbacks => + Sentry.currentHub.lifecycleCallbacks; + + @override + void registerSdkLifecycleCallback( + SdkLifecycleCallback callback) { + Sentry.currentHub.registerSdkLifecycleCallback(callback); + } + + @override + void removeSdkLifecycleCallback( + SdkLifecycleCallback callback) { + Sentry.currentHub.removeSdkLifecycleCallback(callback); + } +} diff --git a/packages/dart/lib/src/integration.dart b/packages/dart/lib/src/integration.dart new file mode 100644 index 0000000000..570388bada --- /dev/null +++ b/packages/dart/lib/src/integration.dart @@ -0,0 +1,14 @@ +import 'dart:async'; + +import 'hub.dart'; +import 'sentry_options.dart'; + +/// Code that provides middlewares, bindings or hooks into certain frameworks or environments, +/// along with code that inserts those bindings and activates them. +abstract class Integration { + /// A Callable method for the Integration interface + FutureOr call(Hub hub, T options); + + /// NoOp by default : only closeable integrations need to override + FutureOr close() {} +} diff --git a/packages/dart/lib/src/invalid_sentry_trace_header_exception.dart b/packages/dart/lib/src/invalid_sentry_trace_header_exception.dart new file mode 100644 index 0000000000..4b953fabfa --- /dev/null +++ b/packages/dart/lib/src/invalid_sentry_trace_header_exception.dart @@ -0,0 +1,7 @@ +class InvalidSentryTraceHeaderException implements Exception { + final String _message; + InvalidSentryTraceHeaderException(this._message); + + @override + String toString() => 'Exception: $_message'; +} diff --git a/packages/dart/lib/src/isolate_error_integration.dart b/packages/dart/lib/src/isolate_error_integration.dart new file mode 100644 index 0000000000..ca617d6380 --- /dev/null +++ b/packages/dart/lib/src/isolate_error_integration.dart @@ -0,0 +1,25 @@ +import 'dart:isolate'; + +import 'hub.dart'; +import 'integration.dart'; +import 'sentry_isolate_extension.dart'; +import 'sentry_options.dart'; + +class IsolateErrorIntegration implements Integration { + RawReceivePort? _receivePort; + + @override + void call(Hub hub, SentryOptions options) { + _receivePort = Isolate.current.addSentryErrorListener(); + options.sdk.addIntegration('isolateErrorIntegration'); + } + + @override + void close() { + final receivePort = _receivePort; + if (receivePort != null) { + receivePort.close(); + Isolate.current.removeSentryErrorListener(receivePort); + } + } +} diff --git a/packages/dart/lib/src/load_dart_debug_images_integration.dart b/packages/dart/lib/src/load_dart_debug_images_integration.dart new file mode 100644 index 0000000000..7d98573019 --- /dev/null +++ b/packages/dart/lib/src/load_dart_debug_images_integration.dart @@ -0,0 +1,171 @@ +import 'dart:typed_data'; + +import 'package:meta/meta.dart'; + +import 'event_processor.dart'; +import 'hint.dart'; +import 'hub.dart'; +import 'integration.dart'; +import 'protocol/debug_image.dart'; +import 'protocol/debug_meta.dart'; +import 'protocol/sentry_event.dart'; +import 'protocol/sentry_level.dart'; +import 'protocol/sentry_stack_trace.dart'; +import 'sentry_options.dart'; + +class LoadDartDebugImagesIntegration extends Integration { + static const integrationName = 'LoadDartDebugImages'; + + @override + void call(Hub hub, SentryOptions options) { + if (options.enableDartSymbolication && + (options.runtimeChecker.isAppObfuscated() || + options.runtimeChecker.isSplitDebugInfoBuild()) && + !options.platform.isWeb) { + options.addEventProcessor( + LoadDartDebugImagesIntegrationEventProcessor(options)); + options.sdk.addIntegration(integrationName); + } + } +} + +@internal +class LoadDartDebugImagesIntegrationEventProcessor implements EventProcessor { + LoadDartDebugImagesIntegrationEventProcessor(this._options); + + final SentryOptions _options; + + // We don't need to always create the debug image, so we cache it here. + DebugImage? _debugImage; + + @override + Future apply(SentryEvent event, Hint hint) async { + final stackTrace = event.stacktrace; + if (stackTrace != null) { + final debugImage = getAppDebugImage(stackTrace); + if (debugImage != null) { + if (event.debugMeta != null) { + event.debugMeta?.addDebugImage(debugImage); + } else { + event.debugMeta = DebugMeta(images: [debugImage]); + } + } + } + + return event; + } + + DebugImage? getAppDebugImage(SentryStackTrace stackTrace) { + // Don't return the debug image if the stack trace doesn't have native info. + if (stackTrace.baseAddr == null || + stackTrace.buildId == null || + !stackTrace.frames.any((f) => f.platform == 'native')) { + return null; + } + try { + _debugImage ??= createDebugImage(stackTrace); + } catch (e, stack) { + _options.log( + SentryLevel.info, + "Couldn't add Dart debug image to event. The event will still be reported.", + exception: e, + stackTrace: stack, + ); + if (_options.automatedTestMode) { + rethrow; + } + } + return _debugImage; + } + + @visibleForTesting + DebugImage? createDebugImage(SentryStackTrace stackTrace) { + if (stackTrace.buildId == null || stackTrace.baseAddr == null) { + _options.log(SentryLevel.warning, + 'Cannot create DebugImage without a build ID and image base address.'); + return null; + } + + // Type and DebugID are required for proper symbolication + late final String type; + late final String debugId; + + // CodeFile is required so that the debug image shows up properly in the UI. + // It doesn't need to exist and is not used for symbolication. + late final String codeFile; + + final platform = _options.platform; + + if (platform.isAndroid || platform.isWindows) { + type = 'elf'; + debugId = _convertBuildIdToDebugId(stackTrace.buildId!, Endian.host); + if (platform.isAndroid) { + codeFile = 'libapp.so'; + } else if (platform.isWindows) { + codeFile = 'data/app.so'; + } + } else if (platform.isIOS || platform.isMacOS) { + type = 'macho'; + debugId = _formatHexToUuid(stackTrace.buildId!); + codeFile = 'App.Framework/App'; + } else { + _options.log( + SentryLevel.warning, + 'Unsupported platform for creating Dart debug images.', + ); + return null; + } + + return DebugImage( + type: type, + imageAddr: stackTrace.baseAddr, + debugId: debugId, + codeId: stackTrace.buildId, + codeFile: codeFile, + ); + } + + /// See https://github.com/getsentry/symbolic/blob/7dc28dd04c06626489c7536cfe8c7be8f5c48804/symbolic-debuginfo/src/elf.rs#L709-L734 + /// Converts an ELF object identifier into a `DebugId`. + /// + /// The identifier data is first truncated or extended to match 16 byte size of + /// Uuids. If the data is declared in little endian, the first three Uuid fields + /// are flipped to match the big endian expected by the breakpad processor. + /// + /// The `DebugId::appendix` field is always `0` for ELF. + String _convertBuildIdToDebugId(String buildId, Endian endian) { + // Make sure that we have exactly UUID_SIZE bytes available + const uuidSize = 16 * 2; + final data = Uint8List(uuidSize); + final len = buildId.length.clamp(0, uuidSize); + data.setAll(0, buildId.codeUnits.take(len)); + + if (endian == Endian.little) { + // The file ELF file targets a little endian architecture. Convert to + // network byte order (big endian) to match the Breakpad processor's + // expectations. For big endian object files, this is not needed. + // To manipulate this as hex, we create an Uint16 view. + final data16 = Uint16List.view(data.buffer); + data16.setRange(0, 4, data16.sublist(0, 4).reversed); + data16.setRange(4, 6, data16.sublist(4, 6).reversed); + data16.setRange(6, 8, data16.sublist(6, 8).reversed); + } + return _formatHexToUuid(String.fromCharCodes(data)); + } + + String _formatHexToUuid(String hex) { + if (hex.length == 36) { + return hex; + } + if (hex.length != 32) { + throw ArgumentError.value(hex, 'hexUUID', + 'Hex input must be a 32-character hexadecimal string'); + } + + return '${hex.substring(0, 8)}-' + '${hex.substring(8, 12)}-' + '${hex.substring(12, 16)}-' + '${hex.substring(16, 20)}-' + '${hex.substring(20)}'; + } +} diff --git a/packages/dart/lib/src/logs_enricher_integration.dart b/packages/dart/lib/src/logs_enricher_integration.dart new file mode 100644 index 0000000000..1885a95c19 --- /dev/null +++ b/packages/dart/lib/src/logs_enricher_integration.dart @@ -0,0 +1,37 @@ +import 'dart:async'; +import 'package:meta/meta.dart'; + +import 'sdk_lifecycle_hooks.dart'; +import 'utils/os_utils.dart'; +import 'integration.dart'; +import 'hub.dart'; +import 'protocol/sentry_log_attribute.dart'; +import 'sentry_options.dart'; + +@internal +class LogsEnricherIntegration extends Integration { + static const integrationName = 'LogsEnricher'; + + @override + FutureOr call(Hub hub, SentryOptions options) { + if (options.enableLogs) { + hub.registerSdkLifecycleCallback( + (event) async { + final os = getSentryOperatingSystem(); + + if (os.name != null) { + event.log.attributes['os.name'] = SentryLogAttribute.string( + os.name ?? '', + ); + } + if (os.version != null) { + event.log.attributes['os.version'] = SentryLogAttribute.string( + os.version ?? '', + ); + } + }, + ); + options.sdk.addIntegration(integrationName); + } + } +} diff --git a/packages/dart/lib/src/noop_client.dart b/packages/dart/lib/src/noop_client.dart new file mode 100644 index 0000000000..2ad9c1a76d --- /dev/null +++ b/packages/dart/lib/src/noop_client.dart @@ -0,0 +1,62 @@ +import 'dart:async'; +import 'dart:typed_data'; +import 'dart:convert'; +import 'package:http/http.dart'; + +class NoOpClient implements Client { + NoOpClient._(); + + static final NoOpClient _instance = NoOpClient._(); + + static final Future _response = Future.value(Response('', 500)); + static final Future _string = Future.value(''); + static final Future _intList = Future.value(Uint8List(0)); + static final Future _streamedResponse = + Future.value(StreamedResponse(Stream.empty(), 500)); + + factory NoOpClient() { + return _instance; + } + + @override + void close() {} + + @override + Future delete( + Uri url, { + Map? headers, + Object? body, + Encoding? encoding, + }) => + _response; + + @override + Future get(url, {Map? headers}) => _response; + + @override + Future head(url, {Map? headers}) => _response; + + @override + Future patch(url, + {Map? headers, body, Encoding? encoding}) => + _response; + + @override + Future post(url, + {Map? headers, body, Encoding? encoding}) => + _response; + + @override + Future put(url, + {Map? headers, body, Encoding? encoding}) => + _response; + + @override + Future read(url, {Map? headers}) => _string; + + @override + Future readBytes(url, {Map? headers}) => _intList; + + @override + Future send(BaseRequest request) => _streamedResponse; +} diff --git a/packages/dart/lib/src/noop_hub.dart b/packages/dart/lib/src/noop_hub.dart new file mode 100644 index 0000000000..de5de1a2b7 --- /dev/null +++ b/packages/dart/lib/src/noop_hub.dart @@ -0,0 +1,159 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; + +import 'hint.dart'; +import 'hub.dart'; +import 'profiling.dart'; +import 'protocol.dart'; +import 'protocol/sentry_feedback.dart'; +import 'scope.dart'; +import 'sdk_lifecycle_hooks.dart'; +import 'sentry_client.dart'; +import 'sentry_options.dart'; +import 'tracing.dart'; + +class NoOpHub implements Hub { + NoOpHub._(); + + static final NoOpHub _instance = NoOpHub._(); + + final _options = SentryOptions.empty(); + + @override + @internal + SentryOptions get options => _options; + + factory NoOpHub() { + return _instance; + } + + @override + void bindClient(SentryClient client) {} + + @override + Future captureEvent( + SentryEvent event, { + dynamic stackTrace, + Hint? hint, + ScopeCallback? withScope, + }) async => + SentryId.empty(); + + @override + Future captureException( + dynamic throwable, { + dynamic stackTrace, + Hint? hint, + SentryMessage? message, + ScopeCallback? withScope, + }) async => + SentryId.empty(); + + @override + Future captureMessage( + String? message, { + SentryLevel? level, + String? template, + List? params, + Hint? hint, + ScopeCallback? withScope, + }) async => + SentryId.empty(); + + @override + Hub clone() => this; + + @override + Future close() async {} + + @override + void configureScope(callback) {} + + @override + bool get isEnabled => false; + + @override + SentryId get lastEventId => SentryId.empty(); + + @override + Future addBreadcrumb(Breadcrumb crumb, {Hint? hint}) async {} + + @override + Future captureTransaction( + SentryTransaction transaction, { + SentryTraceContextHeader? traceContext, + Hint? hint, + }) async => + SentryId.empty(); + + @override + Future captureFeedback( + SentryFeedback feedback, { + Hint? hint, + ScopeCallback? withScope, + }) async => + SentryId.empty(); + + @override + FutureOr captureLog(SentryLog log) async {} + + @override + ISentrySpan startTransaction( + String name, + String operation, { + String? description, + DateTime? startTimestamp, + bool? bindToScope, + bool? waitForChildren, + Duration? autoFinishAfter, + bool? trimEnd, + OnTransactionFinish? onFinish, + Map? customSamplingContext, + }) => + NoOpSentrySpan(); + + @override + ISentrySpan startTransactionWithContext( + SentryTransactionContext transactionContext, { + Map? customSamplingContext, + DateTime? startTimestamp, + bool? bindToScope, + bool? waitForChildren, + Duration? autoFinishAfter, + bool? trimEnd, + OnTransactionFinish? onFinish, + }) => + NoOpSentrySpan(); + + @override + void generateNewTrace() {} + + @override + ISentrySpan? getSpan() => null; + + @override + void setSpanContext(throwable, ISentrySpan span, String transaction) {} + + @internal + @override + set profilerFactory(SentryProfilerFactory? value) {} + + @internal + @override + SentryProfilerFactory? get profilerFactory => null; + + @override + Scope get scope => Scope(_options); + + @override + Map> get lifecycleCallbacks => {}; + + @override + void registerSdkLifecycleCallback( + SdkLifecycleCallback callback) {} + + @override + void removeSdkLifecycleCallback( + SdkLifecycleCallback callback) {} +} diff --git a/packages/dart/lib/src/noop_isolate_error_integration.dart b/packages/dart/lib/src/noop_isolate_error_integration.dart new file mode 100644 index 0000000000..681b97c1a6 --- /dev/null +++ b/packages/dart/lib/src/noop_isolate_error_integration.dart @@ -0,0 +1,15 @@ +import 'hub.dart'; +import 'integration.dart'; +import 'sentry_options.dart'; + +/// NoOp web integration : isolate doesnt' work in browser +class IsolateErrorIntegration extends Integration { + @override + void call(Hub hub, SentryOptions options) {} + + void handleIsolateError( + Hub hub, + SentryOptions options, + dynamic error, + ) {} +} diff --git a/packages/dart/lib/src/noop_log_batcher.dart b/packages/dart/lib/src/noop_log_batcher.dart new file mode 100644 index 0000000000..7d5c94523d --- /dev/null +++ b/packages/dart/lib/src/noop_log_batcher.dart @@ -0,0 +1,12 @@ +import 'dart:async'; + +import 'sentry_log_batcher.dart'; +import 'protocol/sentry_log.dart'; + +class NoopLogBatcher implements SentryLogBatcher { + @override + FutureOr addLog(SentryLog log) {} + + @override + Future flush() async {} +} diff --git a/packages/dart/lib/src/noop_sentry_client.dart b/packages/dart/lib/src/noop_sentry_client.dart new file mode 100644 index 0000000000..a0badcc7d9 --- /dev/null +++ b/packages/dart/lib/src/noop_sentry_client.dart @@ -0,0 +1,79 @@ +import 'dart:async'; + +import 'hint.dart'; +import 'protocol.dart'; +import 'protocol/sentry_feedback.dart'; +import 'scope.dart'; +import 'sdk_lifecycle_hooks.dart'; +import 'sentry_client.dart'; +import 'sentry_envelope.dart'; +import 'sentry_options.dart'; +import 'sentry_trace_context_header.dart'; + +class NoOpSentryClient implements SentryClient { + NoOpSentryClient._(); + + static final NoOpSentryClient _instance = NoOpSentryClient._(); + + factory NoOpSentryClient() { + return _instance; + } + + @override + Future captureEvent( + SentryEvent event, { + dynamic stackTrace, + Scope? scope, + Hint? hint, + }) async => + SentryId.empty(); + + @override + Future captureException( + dynamic exception, { + dynamic stackTrace, + Scope? scope, + Hint? hint, + }) async => + SentryId.empty(); + + @override + Future captureMessage( + String? message, { + SentryLevel? level, + String? template, + List? params, + Scope? scope, + Hint? hint, + }) async => + SentryId.empty(); + + @override + Future captureEnvelope(SentryEnvelope envelope) async => + SentryId.empty(); + + @override + Future close() async {} + + @override + Future captureTransaction( + SentryTransaction transaction, { + Scope? scope, + SentryTraceContextHeader? traceContext, + Hint? hint, + }) async => + SentryId.empty(); + + @override + Future captureFeedback(SentryFeedback feedback, + {Scope? scope, Hint? hint}) async => + SentryId.empty(); + + @override + FutureOr captureLog(SentryLog log, {Scope? scope}) async {} + + final _lifeCycleRegistry = SdkLifecycleRegistry(SentryOptions.empty()); + + @override + SdkLifecycleRegistry get lifeCycleRegistry => _lifeCycleRegistry; +} diff --git a/packages/dart/lib/src/noop_sentry_span.dart b/packages/dart/lib/src/noop_sentry_span.dart new file mode 100644 index 0000000000..0038d0954d --- /dev/null +++ b/packages/dart/lib/src/noop_sentry_span.dart @@ -0,0 +1,103 @@ +import 'hint.dart'; +import 'protocol.dart'; +import 'tracing.dart'; +import 'utils.dart'; + +class NoOpSentrySpan extends ISentrySpan { + NoOpSentrySpan._(); + + static final _instance = NoOpSentrySpan._(); + + static final _spanContext = SentrySpanContext( + traceId: SentryId.empty(), + spanId: SpanId.empty(), + operation: 'noop', + ); + + static final _header = SentryTraceHeader( + SentryId.empty(), + SpanId.empty(), + sampled: false, + ); + + static final _timestamp = getUtcDateTime(); + + factory NoOpSentrySpan() { + return _instance; + } + + @override + Future finish({ + SpanStatus? status, + DateTime? endTimestamp, + Hint? hint, + }) async {} + + @override + void removeData(String key) {} + + @override + void removeTag(String key) {} + + @override + void setData(String key, value) {} + + @override + void setTag(String key, String value) {} + + @override + ISentrySpan startChild( + String operation, { + String? description, + DateTime? startTimestamp, + }) => + NoOpSentrySpan(); + + @override + SentrySpanContext get context => _spanContext; + + @override + String? get origin => null; + + @override + set origin(String? origin) {} + + @override + SpanStatus? get status => null; + + @override + DateTime get startTimestamp => _timestamp; + + @override + DateTime? get endTimestamp => null; + + @override + bool get finished => false; + + @override + dynamic get throwable => null; + + @override + set throwable(throwable) {} + + @override + set status(SpanStatus? status) {} + + @override + SentryTraceHeader toSentryTrace() => _header; + + @override + void setMeasurement(String name, num value, {SentryMeasurementUnit? unit}) {} + + @override + SentryBaggageHeader? toBaggageHeader() => null; + + @override + SentryTraceContextHeader? traceContext() => null; + + @override + SentryTracesSamplingDecision? get samplingDecision => null; + + @override + void scheduleFinish() {} +} diff --git a/packages/dart/lib/src/origin.dart b/packages/dart/lib/src/origin.dart new file mode 100644 index 0000000000..65835e7e0a --- /dev/null +++ b/packages/dart/lib/src/origin.dart @@ -0,0 +1 @@ +export 'origin_io.dart' if (dart.library.js_interop) 'origin_web.dart'; diff --git a/packages/dart/lib/src/origin_io.dart b/packages/dart/lib/src/origin_io.dart new file mode 100644 index 0000000000..0d02495ae3 --- /dev/null +++ b/packages/dart/lib/src/origin_io.dart @@ -0,0 +1,2 @@ +/// request origin, empty out of browser context +String eventOrigin = ''; diff --git a/packages/dart/lib/src/origin_web.dart b/packages/dart/lib/src/origin_web.dart new file mode 100644 index 0000000000..0653054b7a --- /dev/null +++ b/packages/dart/lib/src/origin_web.dart @@ -0,0 +1,4 @@ +import 'package:web/web.dart'; + +/// request origin, used for browser stacktrace +String get eventOrigin => '${window.location.origin}/'; diff --git a/packages/dart/lib/src/performance_collector.dart b/packages/dart/lib/src/performance_collector.dart new file mode 100644 index 0000000000..736534fb03 --- /dev/null +++ b/packages/dart/lib/src/performance_collector.dart @@ -0,0 +1,13 @@ +import '../sentry.dart'; + +abstract class PerformanceCollector {} + +/// Used for collecting continuous data about vitals (slow, frozen frames, etc.) +/// during a transaction/span. +abstract class PerformanceContinuousCollector extends PerformanceCollector { + Future onSpanStarted(ISentrySpan span); + + Future onSpanFinished(ISentrySpan span, DateTime endTimestamp); + + void clear(); +} diff --git a/packages/dart/lib/src/platform/_io_platform.dart b/packages/dart/lib/src/platform/_io_platform.dart new file mode 100644 index 0000000000..fa8f0f5ea2 --- /dev/null +++ b/packages/dart/lib/src/platform/_io_platform.dart @@ -0,0 +1,33 @@ +import 'dart:io' as io; + +import 'platform.dart'; + +/// [Platform] implementation that delegates directly to `dart:io`. +class PlatformBase { + const PlatformBase(); + + OperatingSystem get operatingSystem { + switch (io.Platform.operatingSystem) { + case 'macos': + return OperatingSystem.macos; + case 'windows': + return OperatingSystem.windows; + case 'linux': + return OperatingSystem.linux; + case 'android': + return OperatingSystem.android; + case 'ios': + return OperatingSystem.ios; + case 'fuchsia': + return OperatingSystem.fuchsia; + default: + return OperatingSystem.unknown; + } + } + + String? get operatingSystemVersion => io.Platform.operatingSystemVersion; + + String get localHostname => io.Platform.localHostname; + + bool get isWeb => false; +} diff --git a/packages/dart/lib/src/platform/_web_platform.dart b/packages/dart/lib/src/platform/_web_platform.dart new file mode 100644 index 0000000000..56325fa25b --- /dev/null +++ b/packages/dart/lib/src/platform/_web_platform.dart @@ -0,0 +1,45 @@ +import 'package:web/web.dart' as web; + +import 'platform.dart'; + +/// [Platform] implementation that delegates to `dart:web`. +class PlatformBase { + const PlatformBase(); + + bool get isWeb => true; + + OperatingSystem get operatingSystem { + final navigatorPlatform = web.window.navigator.platform.toLowerCase(); + if (navigatorPlatform.startsWith('mac')) { + return OperatingSystem.macos; + } + if (navigatorPlatform.startsWith('win')) { + return OperatingSystem.windows; + } + if (navigatorPlatform.contains('iphone') || + navigatorPlatform.contains('ipad') || + navigatorPlatform.contains('ipod')) { + return OperatingSystem.ios; + } + if (navigatorPlatform.contains('android')) { + return OperatingSystem.android; + } + if (navigatorPlatform.contains('fuchsia')) { + return OperatingSystem.fuchsia; + } + + // Since some phones can report a window.navigator.platform as Linux, fall + // back to use CSS to disambiguate Android vs Linux desktop. If the CSS + // indicates that a device has a "fine pointer" (mouse) as the primary + // pointing device, then we'll assume desktop linux, and otherwise we'll + // assume Android. + if (web.window.matchMedia('only screen and (pointer: fine)').matches) { + return OperatingSystem.linux; + } + return OperatingSystem.android; + } + + String? get operatingSystemVersion => null; + + String get localHostname => web.window.location.hostname; +} diff --git a/packages/dart/lib/src/platform/mock_platform.dart b/packages/dart/lib/src/platform/mock_platform.dart new file mode 100644 index 0000000000..99b3ef81cc --- /dev/null +++ b/packages/dart/lib/src/platform/mock_platform.dart @@ -0,0 +1,52 @@ +import 'platform.dart'; + +class MockPlatform extends Platform { + @override + late final bool isWeb; + + @override + late final OperatingSystem operatingSystem; + + @override + late final String? operatingSystemVersion; + + @override + late final bool supportsNativeIntegration; + + MockPlatform( + {OperatingSystem? operatingSystem, + String? operatingSystemVersion, + bool? isWeb, + bool? supportsNativeIntegration}) { + this.isWeb = isWeb ?? super.isWeb; + this.operatingSystem = operatingSystem ?? super.operatingSystem; + this.operatingSystemVersion = + operatingSystemVersion ?? super.operatingSystemVersion; + this.supportsNativeIntegration = + supportsNativeIntegration ?? super.supportsNativeIntegration; + } + + factory MockPlatform.android({bool isWeb = false}) { + return MockPlatform(operatingSystem: OperatingSystem.android, isWeb: isWeb); + } + + factory MockPlatform.iOS({bool isWeb = false}) { + return MockPlatform(operatingSystem: OperatingSystem.ios, isWeb: isWeb); + } + + factory MockPlatform.macOS({bool isWeb = false}) { + return MockPlatform(operatingSystem: OperatingSystem.macos, isWeb: isWeb); + } + + factory MockPlatform.linux({bool isWeb = false}) { + return MockPlatform(operatingSystem: OperatingSystem.linux, isWeb: isWeb); + } + + factory MockPlatform.windows({bool isWeb = false}) { + return MockPlatform(operatingSystem: OperatingSystem.windows, isWeb: isWeb); + } + + factory MockPlatform.fuchsia({bool isWeb = false}) { + return MockPlatform(operatingSystem: OperatingSystem.fuchsia, isWeb: isWeb); + } +} diff --git a/packages/dart/lib/src/platform/platform.dart b/packages/dart/lib/src/platform/platform.dart new file mode 100644 index 0000000000..ea0ed3fdf7 --- /dev/null +++ b/packages/dart/lib/src/platform/platform.dart @@ -0,0 +1,30 @@ +import '_io_platform.dart' if (dart.library.js_interop) '_web_platform.dart' + as impl; + +class Platform extends impl.PlatformBase { + const Platform(); + + bool get isLinux => operatingSystem == OperatingSystem.linux; + + bool get isMacOS => operatingSystem == OperatingSystem.macos; + + bool get isWindows => operatingSystem == OperatingSystem.windows; + + bool get isAndroid => operatingSystem == OperatingSystem.android; + + bool get isIOS => operatingSystem == OperatingSystem.ios; + + bool get isFuchsia => operatingSystem == OperatingSystem.fuchsia; + + bool get supportsNativeIntegration => !isFuchsia; +} + +enum OperatingSystem { + android, + fuchsia, + ios, + linux, + macos, + windows, + unknown, +} diff --git a/packages/dart/lib/src/profiling.dart b/packages/dart/lib/src/profiling.dart new file mode 100644 index 0000000000..d0ed997313 --- /dev/null +++ b/packages/dart/lib/src/profiling.dart @@ -0,0 +1,22 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; + +import '../sentry.dart'; + +@internal +abstract class SentryProfilerFactory { + SentryProfiler? startProfiler(SentryTransactionContext context); +} + +@internal +abstract class SentryProfiler { + Future finishFor(SentryTransaction transaction); + void dispose(); +} + +// See https://develop.sentry.dev/sdk/profiles/ +@internal +abstract class SentryProfileInfo { + SentryEnvelopeItem asEnvelopeItem(); +} diff --git a/packages/dart/lib/src/propagation_context.dart b/packages/dart/lib/src/propagation_context.dart new file mode 100644 index 0000000000..b75a897383 --- /dev/null +++ b/packages/dart/lib/src/propagation_context.dart @@ -0,0 +1,57 @@ +import 'package:meta/meta.dart'; + +import '../sentry.dart'; + +@internal +class PropagationContext { + /// Either represents the incoming `traceId` or the `traceId` generated by the current SDK, if there was no incoming trace. + SentryId traceId = SentryId.newId(); + + /// The dynamic sampling context. + SentryBaggage? baggage; + + bool? _sampled; + + /// Indicates whether the current trace is sampled or not. + /// + /// This flag follows the lifecycle of a trace: + /// * It starts as `null` (undecided). + /// * The **first** transaction that receives a sampling decision (root + /// transaction) sets the flag to the decided value. Subsequent + /// transactions for the same trace MUST NOT change the value. + /// * When a new trace is started (i.e. when a new `traceId` is generated), + /// the flag is reset back to `null`. + /// + /// The flag is propagated via the `sentry-trace` header so that downstream + /// services can honour the original sampling decision. + bool? get sampled => _sampled; + + /// Applies the sampling decision exactly once per trace. + void applySamplingDecision(bool sampled) { + _sampled ??= sampled; + } + + /// Random number generated for sampling decisions. + /// + /// This value must be generated **once per trace** and reused across all + /// child spans and transactions that belong to the same trace. It is reset + /// whenever a new trace is started. + double? sampleRand; + + /// Starts a brand-new trace (new ID, new sampling value & sampled state). + void resetTrace() { + traceId = SentryId.newId(); + sampleRand = null; + _sampled = null; + } + + /// Baggage header to attach to http headers. + SentryBaggageHeader? toBaggageHeader() => + baggage != null ? SentryBaggageHeader.fromBaggage(baggage!) : null; + + /// Sentry trace header to attach to http headers. + SentryTraceHeader toSentryTrace() => generateSentryTraceHeader( + traceId: traceId, + sampled: sampled, + ); +} diff --git a/packages/dart/lib/src/protocol.dart b/packages/dart/lib/src/protocol.dart new file mode 100644 index 0000000000..07b7e0b30b --- /dev/null +++ b/packages/dart/lib/src/protocol.dart @@ -0,0 +1,46 @@ +export 'protocol/breadcrumb.dart'; +export 'protocol/contexts.dart'; +export 'protocol/debug_image.dart'; +export 'protocol/debug_meta.dart'; +export 'protocol/dsn.dart'; +export 'protocol/max_body_size.dart'; +export 'protocol/mechanism.dart'; +export 'protocol/sdk_info.dart'; +export 'protocol/sdk_version.dart'; +export 'protocol/sentry_app.dart'; +export 'protocol/sentry_baggage_header.dart'; +export 'protocol/sentry_browser.dart'; +export 'protocol/sentry_culture.dart'; +export 'protocol/sentry_device.dart'; +export 'protocol/sentry_event.dart'; +export 'protocol/sentry_exception.dart'; +export 'protocol/sentry_geo.dart'; +export 'protocol/sentry_gpu.dart'; +export 'protocol/sentry_id.dart'; +export 'protocol/sentry_level.dart'; +export 'protocol/sentry_message.dart'; +export 'protocol/sentry_operating_system.dart'; +export 'protocol/sentry_package.dart'; +export 'protocol/sentry_request.dart'; +export 'protocol/sentry_response.dart'; +export 'protocol/sentry_runtime.dart'; +export 'protocol/sentry_span.dart'; +export 'protocol/sentry_stack_frame.dart'; +export 'protocol/sentry_stack_trace.dart'; +export 'protocol/sentry_thread.dart'; +export 'protocol/sentry_trace_context.dart'; +export 'protocol/sentry_trace_header.dart'; +export 'protocol/sentry_transaction.dart'; +export 'protocol/sentry_transaction_info.dart'; +export 'protocol/sentry_transaction_name_source.dart'; +export 'protocol/sentry_user.dart'; +export 'protocol/sentry_view_hierarchy.dart'; +export 'protocol/sentry_view_hierarchy_element.dart'; +export 'protocol/span_id.dart'; +export 'protocol/span_status.dart'; +export 'sentry_event_like.dart'; +export 'protocol/sentry_feature_flag.dart'; +export 'protocol/sentry_feature_flags.dart'; +export 'protocol/sentry_log.dart'; +export 'protocol/sentry_log_level.dart'; +export 'protocol/sentry_log_attribute.dart'; diff --git a/packages/dart/lib/src/protocol/access_aware_map.dart b/packages/dart/lib/src/protocol/access_aware_map.dart new file mode 100644 index 0000000000..c8ea7b2395 --- /dev/null +++ b/packages/dart/lib/src/protocol/access_aware_map.dart @@ -0,0 +1,53 @@ +import 'dart:collection'; + +import 'package:meta/meta.dart'; + +@internal +class AccessAwareMap extends MapBase { + AccessAwareMap(this._map); + + final Map _map; + final Set _accessedKeysWithValues = {}; + + Set get accessedKeysWithValues => _accessedKeysWithValues; + + @override + V? operator [](Object? key) { + if (key is String && _map.containsKey(key)) { + _accessedKeysWithValues.add(key); + } + return _map[key]; + } + + @override + void operator []=(String key, V value) { + _map[key] = value; + } + + @override + void clear() { + _map.clear(); + _accessedKeysWithValues.clear(); + } + + @override + Iterable get keys => _map.keys; + + @override + V? remove(Object? key) { + return _map.remove(key); + } + + Map? notAccessed() { + if (_accessedKeysWithValues.length == _map.length) { + return null; + } + Map unknown = _map.keys + .where((key) => !_accessedKeysWithValues.contains(key)) + .fold>({}, (map, key) { + map[key] = _map[key]; + return map; + }); + return unknown.isNotEmpty ? unknown : null; + } +} diff --git a/packages/dart/lib/src/protocol/breadcrumb.dart b/packages/dart/lib/src/protocol/breadcrumb.dart new file mode 100644 index 0000000000..5cb0f6e4f0 --- /dev/null +++ b/packages/dart/lib/src/protocol/breadcrumb.dart @@ -0,0 +1,222 @@ +import 'package:meta/meta.dart'; + +import '../utils.dart'; +import '../protocol.dart'; +import 'access_aware_map.dart'; + +/// Structured data to describe more information prior to the event captured. +/// See `Sentry.captureEvent()`. +/// +/// The outgoing JSON representation is: +/// +/// ``` +/// { +/// "timestamp": 1000 +/// "message": "message", +/// "category": "category", +/// "data": {"key": "value"}, +/// "level": "info", +/// "type": "default" +/// } +/// ``` +/// See also: +/// * https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/ +class Breadcrumb { + /// Creates a breadcrumb that can be attached to an [SentryEvent]. + Breadcrumb({ + this.message, + DateTime? timestamp, + this.category, + this.data, + SentryLevel? level, + this.type, + this.unknown, + }) : timestamp = timestamp ?? getUtcDateTime(), + level = level ?? SentryLevel.info; + + factory Breadcrumb.http({ + required Uri url, + required String method, + int? statusCode, + String? reason, + Duration? requestDuration, + SentryLevel? level, + DateTime? timestamp, + + // Size of the request body in bytes + int? requestBodySize, + + // Size of the response body in bytes + int? responseBodySize, + String? httpQuery, + String? httpFragment, + }) { + // The timestamp is used as the request-end time, so we need to set it right + // now and not rely on the default constructor. + timestamp ??= getUtcDateTime(); + + return Breadcrumb( + type: 'http', + category: 'http', + level: level, + timestamp: timestamp, + data: { + 'url': url.toString(), + 'method': method, + if (statusCode != null) 'status_code': statusCode, + if (reason != null) 'reason': reason, + if (requestDuration != null) 'duration': requestDuration.toString(), + if (requestBodySize != null) 'request_body_size': requestBodySize, + if (responseBodySize != null) 'response_body_size': responseBodySize, + if (httpQuery != null) 'http.query': httpQuery, + if (httpFragment != null) 'http.fragment': httpFragment, + if (requestDuration != null) + 'start_timestamp': + timestamp.millisecondsSinceEpoch - requestDuration.inMilliseconds, + if (requestDuration != null) + 'end_timestamp': timestamp.millisecondsSinceEpoch, + }, + ); + } + + factory Breadcrumb.console({ + String? message, + SentryLevel? level, + DateTime? timestamp, + Map? data, + }) { + return Breadcrumb( + message: message, + level: level, + category: 'console', + type: 'debug', + timestamp: timestamp, + data: data, + ); + } + + factory Breadcrumb.userInteraction({ + String? message, + SentryLevel? level, + DateTime? timestamp, + Map? data, + required String subCategory, + String? viewId, + String? viewClass, + }) { + return Breadcrumb( + message: message, + level: level, + category: 'ui.$subCategory', + type: 'user', + timestamp: timestamp, + data: { + if (viewId != null) 'view.id': viewId, + if (viewClass != null) 'view.class': viewClass, + if (data != null) ...data, + }, + ); + } + + /// Describes the breadcrumb. + /// + /// This field is optional and may be set to null. + String? message; + + /// A dot-separated string describing the source of the breadcrumb, e.g. "ui.click". + /// + /// This field is optional and may be set to null. + String? category; + + /// Data associated with the breadcrumb. + /// + /// The contents depend on the [type] of breadcrumb. + /// + /// This field is optional and may be set to null. + /// + /// See also: + /// + /// * https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types + Map? data; + + /// Severity of the breadcrumb. + /// + /// This field is optional and may be set to null. + SentryLevel? level; + + /// Describes what type of breadcrumb this is. + /// + /// Possible values: "default", "http", "navigation". + /// + /// This field is optional and may be set to null. + /// + /// See also: + /// + /// * https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types + String? type; + + /// The time the breadcrumb was recorded. + /// + /// This field is required, it must not be null. + /// + /// The value is submitted to Sentry with second precision. + DateTime timestamp; + + @internal + final Map? unknown; + + /// Deserializes a [Breadcrumb] from JSON [Map]. + factory Breadcrumb.fromJson(Map jsonData) { + final json = AccessAwareMap(jsonData); + + final levelName = json['level']; + final timestamp = json['timestamp']; + + var data = json['data']; + if (data != null) { + data = Map.from(data as Map); + } + return Breadcrumb( + timestamp: timestamp != null ? DateTime.tryParse(timestamp) : null, + message: json['message'], + category: json['category'], + data: data, + level: levelName != null ? SentryLevel.fromName(levelName) : null, + type: json['type'], + unknown: json.notAccessed(), + ); + } + + /// Converts this breadcrumb to a map that can be serialized to JSON according + /// to the Sentry protocol. + Map toJson() { + return { + ...?unknown, + 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), + if (message != null) 'message': message, + if (category != null) 'category': category, + if (data?.isNotEmpty ?? false) 'data': data, + if (level != null) 'level': level!.name, + if (type != null) 'type': type, + }; + } + + @Deprecated('Assign values directly to the instance.') + Breadcrumb copyWith({ + String? message, + String? category, + Map? data, + SentryLevel? level, + String? type, + DateTime? timestamp, + }) => + Breadcrumb( + message: message ?? this.message, + category: category ?? this.category, + data: data ?? this.data, + level: level ?? this.level, + type: type ?? this.type, + timestamp: timestamp ?? this.timestamp, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/contexts.dart b/packages/dart/lib/src/protocol/contexts.dart new file mode 100644 index 0000000000..1a68843baf --- /dev/null +++ b/packages/dart/lib/src/protocol/contexts.dart @@ -0,0 +1,349 @@ +import 'dart:collection'; +import 'package:meta/meta.dart'; + +import '../protocol.dart'; +import 'sentry_feedback.dart'; + +/// The context interfaces provide additional context data. +/// +/// Typically this is data related to the Device, OS, Runtime, App, +/// Browser, GPU and State context. +/// +/// See also: https://develop.sentry.dev/sdk/event-payloads/contexts/. +class Contexts extends MapView { + Contexts({ + SentryDevice? device, + SentryOperatingSystem? operatingSystem, + List? runtimes, + SentryApp? app, + SentryBrowser? browser, + SentryGpu? gpu, + SentryCulture? culture, + SentryTraceContext? trace, + SentryResponse? response, + SentryFeedback? feedback, + SentryFeatureFlags? flags, + }) : super({ + SentryDevice.type: device, + SentryOperatingSystem.type: operatingSystem, + SentryRuntime.listType: List.from(runtimes ?? []), + SentryApp.type: app, + SentryBrowser.type: browser, + SentryGpu.type: gpu, + SentryCulture.type: culture, + SentryTraceContext.type: trace, + SentryResponse.type: response, + SentryFeedback.type: feedback, + SentryFeatureFlags.type: flags, + }); + + /// Deserializes [Contexts] from JSON [Map]. + factory Contexts.fromJson(Map data) { + final contexts = Contexts( + device: data[SentryDevice.type] != null + ? SentryDevice.fromJson(Map.from(data[SentryDevice.type])) + : null, + operatingSystem: data[SentryOperatingSystem.type] != null + ? SentryOperatingSystem.fromJson( + Map.from(data[SentryOperatingSystem.type])) + : null, + app: data[SentryApp.type] != null + ? SentryApp.fromJson(Map.from(data[SentryApp.type])) + : null, + browser: data[SentryBrowser.type] != null + ? SentryBrowser.fromJson(Map.from(data[SentryBrowser.type])) + : null, + culture: data[SentryCulture.type] != null + ? SentryCulture.fromJson(Map.from(data[SentryCulture.type])) + : null, + gpu: data[SentryGpu.type] != null + ? SentryGpu.fromJson(Map.from(data[SentryGpu.type])) + : null, + trace: data[SentryTraceContext.type] != null + ? SentryTraceContext.fromJson(Map.from(data[SentryTraceContext.type])) + : null, + runtimes: data[SentryRuntime.type] != null + ? [SentryRuntime.fromJson(Map.from(data[SentryRuntime.type]))] + : null, + response: data[SentryResponse.type] != null + ? SentryResponse.fromJson(Map.from(data[SentryResponse.type])) + : null, + feedback: data[SentryFeedback.type] != null + ? SentryFeedback.fromJson(Map.from(data[SentryFeedback.type])) + : null, + flags: data[SentryFeatureFlags.type] != null + ? SentryFeatureFlags.fromJson(Map.from(data[SentryFeatureFlags.type])) + : null, + ); + + data.keys + .where((key) => !defaultFields.contains(key) && data[key] != null) + .forEach((key) => contexts[key] = data[key]); + + return contexts; + } + + /// This describes the device that caused the event. + SentryDevice? get device => this[SentryDevice.type]; + + set device(SentryDevice? device) => this[SentryDevice.type] = device; + + /// Describes the operating system on which the event was created. + /// + /// In web contexts, this is the operating system of the browse + /// (normally pulled from the User-Agent string). + SentryOperatingSystem? get operatingSystem => + this[SentryOperatingSystem.type]; + + set operatingSystem(SentryOperatingSystem? operatingSystem) => + this[SentryOperatingSystem.type] = operatingSystem; + + /// Describes an immutable list of runtimes in more detail + /// (for instance if you have a Flutter application running + /// on top of Android). + List get runtimes => + List.unmodifiable(this[SentryRuntime.listType] ?? []); + + set runtimes(List runtimes) => + this[SentryRuntime.listType] = List.from(runtimes); + + void addRuntime(SentryRuntime runtime) => + this[SentryRuntime.listType].add(runtime); + + void removeRuntime(SentryRuntime runtime) => + this[SentryRuntime.listType].remove(runtime); + + /// App context describes the application. + /// + /// As opposed to the runtime, this is the actual application that was + /// running and carries metadata about the current session. + SentryApp? get app => this[SentryApp.type]; + + set app(SentryApp? app) => this[SentryApp.type] = app; + + /// Carries information about the browser or user agent for web-related + /// errors. + /// + /// This can either be the browser this event ocurred in, or the user + /// agent of a web request that triggered the event. + SentryBrowser? get browser => this[SentryBrowser.type]; + + set browser(SentryBrowser? browser) => this[SentryBrowser.type] = browser; + + /// Culture Context describes certain properties of the culture in which the + /// software is used. + SentryCulture? get culture => this[SentryCulture.type]; + + set culture(SentryCulture? culture) => this[SentryCulture.type] = culture; + + /// GPU context describes the GPU of the device. + SentryGpu? get gpu => this[SentryGpu.type]; + + set gpu(SentryGpu? gpu) => this[SentryGpu.type] = gpu; + + /// The tracing context of the transaction + SentryTraceContext? get trace => this[SentryTraceContext.type]; + + set trace(SentryTraceContext? trace) => this[SentryTraceContext.type] = trace; + + /// Response context for a HTTP response. Not added automatically. + SentryResponse? get response => this[SentryResponse.type]; + + /// Use [Hint.response] in `beforeSend/beforeSendTransaction` to populate this value. + set response(SentryResponse? value) => this[SentryResponse.type] = value; + + /// Feedback context for a feedback event. + SentryFeedback? get feedback => this[SentryFeedback.type]; + + set feedback(SentryFeedback? value) => this[SentryFeedback.type] = value; + + /// Feature flags context for a feature flag event. + SentryFeatureFlags? get flags => this[SentryFeatureFlags.type]; + + set flags(SentryFeatureFlags? value) => this[SentryFeatureFlags.type] = value; + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + final json = {}; + + forEach((key, value) { + if (value == null) return; + switch (key) { + case SentryDevice.type: + final deviceMap = device?.toJson(); + if (deviceMap?.isNotEmpty ?? false) { + json[SentryDevice.type] = deviceMap; + } + break; + case SentryOperatingSystem.type: + final osMap = operatingSystem?.toJson(); + if (osMap?.isNotEmpty ?? false) { + json[SentryOperatingSystem.type] = osMap; + } + break; + + case SentryApp.type: + final appMap = app?.toJson(); + if (appMap?.isNotEmpty ?? false) { + json[SentryApp.type] = appMap; + } + break; + + case SentryBrowser.type: + final browserMap = browser?.toJson(); + if (browserMap?.isNotEmpty ?? false) { + json[SentryBrowser.type] = browserMap; + } + break; + + case SentryCulture.type: + final cultureMap = culture?.toJson(); + if (cultureMap?.isNotEmpty ?? false) { + json[SentryCulture.type] = cultureMap; + } + break; + + case SentryGpu.type: + final gpuMap = gpu?.toJson(); + if (gpuMap?.isNotEmpty ?? false) { + json[SentryGpu.type] = gpuMap; + } + break; + + case SentryResponse.type: + final responseMap = response?.toJson(); + if (responseMap?.isNotEmpty ?? false) { + json[SentryResponse.type] = responseMap; + } + break; + + case SentryTraceContext.type: + final traceMap = trace?.toJson(); + if (traceMap?.isNotEmpty ?? false) { + json[SentryTraceContext.type] = traceMap; + } + break; + + case SentryFeedback.type: + final feedbackMap = feedback?.toJson(); + if (feedbackMap?.isNotEmpty ?? false) { + json[SentryFeedback.type] = feedbackMap; + } + break; + + case SentryRuntime.listType: + if (runtimes.length == 1) { + final runtime = runtimes[0]; + final runtimeMap = runtime.toJson(); + if (runtimeMap.isNotEmpty) { + final key = runtime.key ?? SentryRuntime.type; + + json[key] = runtimeMap; + } + } else if (runtimes.length > 1) { + for (final runtime in runtimes) { + final runtimeMap = runtime.toJson(); + if (runtimeMap.isNotEmpty) { + var key = runtime.key ?? runtime.name!.toLowerCase(); + + if (json.containsKey(key)) { + var k = 0; + while (json.containsKey(key)) { + key = '$key$k'; + k++; + } + } + json[key] = runtimeMap + ..addAll({'type': SentryRuntime.type}); + } + } + } + + break; + + case SentryFeatureFlags.type: + final flagsMap = flags?.toJson(); + if (flagsMap?.isNotEmpty ?? false) { + json[SentryFeatureFlags.type] = flagsMap; + } + break; + + default: + if (value != null) { + json[key] = value; + } + } + }); + + return json; + } + + @Deprecated('Will be removed in a future version.') + Contexts clone() { + final copy = Contexts( + device: device?.clone(), + operatingSystem: operatingSystem?.clone(), + app: app?.clone(), + browser: browser?.clone(), + culture: culture?.clone(), + gpu: gpu?.clone(), + trace: trace?.clone(), + response: response?.clone(), + runtimes: runtimes.map((runtime) => runtime.clone()).toList(), + feedback: feedback?.clone(), + flags: flags?.clone(), + )..addEntries( + entries.where((element) => !defaultFields.contains(element.key)), + ); + + return copy; + } + + @Deprecated( + 'Will be removed in a future version. Assign values directly to the instance.') + Contexts copyWith({ + SentryDevice? device, + SentryOperatingSystem? operatingSystem, + List? runtimes, + SentryApp? app, + SentryBrowser? browser, + SentryCulture? culture, + SentryGpu? gpu, + SentryTraceContext? trace, + SentryResponse? response, + SentryFeedback? feedback, + SentryFeatureFlags? flags, + }) => + Contexts( + device: device ?? this.device, + operatingSystem: operatingSystem ?? this.operatingSystem, + runtimes: runtimes ?? + List.from(this[SentryRuntime.listType] ?? []), + app: app ?? this.app, + browser: browser ?? this.browser, + gpu: gpu ?? this.gpu, + culture: culture ?? this.culture, + trace: trace ?? this.trace, + response: response ?? this.response, + feedback: feedback ?? this.feedback, + flags: flags ?? this.flags, + )..addEntries( + entries.where((element) => !defaultFields.contains(element.key)), + ); + + @internal + static const defaultFields = [ + SentryApp.type, + SentryDevice.type, + SentryOperatingSystem.type, + SentryRuntime.listType, + SentryRuntime.type, + SentryGpu.type, + SentryBrowser.type, + SentryCulture.type, + SentryTraceContext.type, + SentryResponse.type, + SentryFeedback.type, + SentryFeatureFlags.type, + ]; +} diff --git a/packages/dart/lib/src/protocol/debug_image.dart b/packages/dart/lib/src/protocol/debug_image.dart new file mode 100644 index 0000000000..0791b28099 --- /dev/null +++ b/packages/dart/lib/src/protocol/debug_image.dart @@ -0,0 +1,148 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +/// The list of debug images contains all dynamic libraries loaded into +/// the process and their memory addresses. +/// Instruction addresses in the Stack Trace are mapped into the list of debug +/// images in order to retrieve debug files for symbolication. +/// There are two kinds of debug images: +// +/// Native debug images with types macho, elf, and pe +/// Android debug images with type proguard +/// more details : https://develop.sentry.dev/sdk/event-payloads/debugmeta/ +class DebugImage { + String? uuid; + + /// Required. Type of the debug image. + String type; + + // Name of the image. Sentry-cocoa only. + String? name; + + /// Required. Identifier of the dynamic library or executable. It is the value of the LC_UUID load command in the Mach header, formatted as UUID. + String? debugId; + + /// Required. Memory address, at which the image is mounted in the virtual address space of the process. + /// Should be a string in hex representation prefixed with "0x". + String? imageAddr; + + /// Optional. Preferred load address of the image in virtual memory, as declared in the headers of the image. + /// When loading an image, the operating system may still choose to place it at a different address. + String? imageVmAddr; + + /// Required. The size of the image in virtual memory. If missing, Sentry will assume that the image spans up to the next image, which might lead to invalid stack traces. + int? imageSize; + + /// OptionalName or absolute path to the dSYM file containing debug information for this image. This value might be required to retrieve debug files from certain symbol servers. + String? debugFile; + + /// Optional. The absolute path to the dynamic library or executable. This helps to locate the file if it is missing on Sentry. + String? codeFile; + + /// Optional Architecture of the module. If missing, this will be backfilled by Sentry. + String? arch; + + /// Optional. Identifier of the dynamic library or executable. It is the value of the LC_UUID load command in the Mach header, formatted as UUID. Can be empty for Mach images, as it is equivalent to the debug identifier. + String? codeId; + + /// MachO CPU subtype identifier. + int? cpuSubtype; + + /// MachO CPU type identifier. + int? cpuType; + + @internal + final Map? unknown; + + DebugImage({ + required this.type, + this.name, + this.imageAddr, + this.imageVmAddr, + this.debugId, + this.debugFile, + this.imageSize, + this.uuid, + this.codeFile, + this.arch, + this.codeId, + this.cpuType, + this.cpuSubtype, + this.unknown, + }); + + /// Deserializes a [DebugImage] from JSON [Map]. + factory DebugImage.fromJson(Map data) { + final json = AccessAwareMap(data); + return DebugImage( + type: json['type'], + name: json['name'], + imageAddr: json['image_addr'], + imageVmAddr: json['image_vmaddr'], + debugId: json['debug_id'], + debugFile: json['debug_file'], + imageSize: json['image_size'], + uuid: json['uuid'], + codeFile: json['code_file'], + arch: json['arch'], + codeId: json['code_id'], + cpuType: json['cpu_type'], + cpuSubtype: json['cpu_subtype'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + 'type': type, + if (uuid != null) 'uuid': uuid, + if (debugId != null) 'debug_id': debugId, + if (name != null) 'name': name, + if (debugFile != null) 'debug_file': debugFile, + if (codeFile != null) 'code_file': codeFile, + if (imageAddr != null) 'image_addr': imageAddr, + if (imageVmAddr != null) 'image_vmaddr': imageVmAddr, + if (imageSize != null) 'image_size': imageSize, + if (arch != null) 'arch': arch, + if (codeId != null) 'code_id': codeId, + if (cpuType != null) 'cpu_type': cpuType, + if (cpuSubtype != null) 'cpu_subtype': cpuSubtype, + }; + } + + @Deprecated('Assign values directly to the instance.') + DebugImage copyWith({ + String? uuid, + String? name, + String? type, + String? debugId, + String? debugFile, + String? codeFile, + String? imageAddr, + String? imageVmAddr, + int? imageSize, + String? arch, + String? codeId, + int? cpuType, + int? cpuSubtype, + }) => + DebugImage( + uuid: uuid ?? this.uuid, + name: name ?? this.name, + type: type ?? this.type, + debugId: debugId ?? this.debugId, + debugFile: debugFile ?? this.debugFile, + codeFile: codeFile ?? this.codeFile, + imageAddr: imageAddr ?? this.imageAddr, + imageVmAddr: imageVmAddr ?? this.imageVmAddr, + imageSize: imageSize ?? this.imageSize, + arch: arch ?? this.arch, + codeId: codeId ?? this.codeId, + cpuType: cpuType ?? this.cpuType, + cpuSubtype: cpuSubtype ?? this.cpuSubtype, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/debug_meta.dart b/packages/dart/lib/src/protocol/debug_meta.dart new file mode 100644 index 0000000000..b82164300a --- /dev/null +++ b/packages/dart/lib/src/protocol/debug_meta.dart @@ -0,0 +1,69 @@ +import 'package:meta/meta.dart'; + +import '../protocol.dart'; +import 'access_aware_map.dart'; + +/// The debug meta interface carries debug information for processing errors and crash reports. +class DebugMeta { + /// An object describing the system SDK. + SdkInfo? sdk; + + List? _images; + + /// The immutable list of debug images contains all dynamic libraries loaded + /// into the process and their memory addresses. + /// Instruction addresses in the Stack Trace are mapped into the list of debug + /// images in order to retrieve debug files for symbolication. + List get images => List.unmodifiable(_images ?? const []); + + void addDebugImage(DebugImage debugImage) { + _images ??= []; + _images?.add(debugImage); + } + + DebugMeta({this.sdk, List? images, this.unknown}) + : _images = images; + + @internal + final Map? unknown; + + /// Deserializes a [DebugMeta] from JSON [Map]. + factory DebugMeta.fromJson(Map data) { + final json = AccessAwareMap(data); + final sdkInfoJson = json['sdk_info']; + final debugImagesJson = json['images'] as List?; + return DebugMeta( + sdk: sdkInfoJson != null ? SdkInfo.fromJson(sdkInfoJson) : null, + images: debugImagesJson + ?.map((debugImageJson) => + DebugImage.fromJson(debugImageJson as Map)) + .toList(), + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + final sdkInfo = sdk?.toJson(); + return { + ...?unknown, + if (sdkInfo?.isNotEmpty ?? false) 'sdk_info': sdkInfo, + if (_images?.isNotEmpty ?? false) + 'images': _images! + .map((e) => e.toJson()) + .where((element) => element.isNotEmpty) + .toList(growable: false), + }; + } + + @Deprecated('Assign values directly to the instance.') + DebugMeta copyWith({ + SdkInfo? sdk, + List? images, + }) => + DebugMeta( + sdk: sdk ?? this.sdk, + images: images ?? _images, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/dsn.dart b/packages/dart/lib/src/protocol/dsn.dart new file mode 100644 index 0000000000..c3ec5093c8 --- /dev/null +++ b/packages/dart/lib/src/protocol/dsn.dart @@ -0,0 +1,68 @@ +import 'package:meta/meta.dart'; + +/// The Data Source Name (DSN) tells the SDK where to send the events +@immutable +class Dsn { + const Dsn({ + required this.publicKey, + required this.projectId, + this.uri, + this.secretKey, + }); + + /// The Sentry.io public key for the project. + final String publicKey; + + /// The Sentry.io secret key for the project. + final String? secretKey; + + /// The ID issued by Sentry.io to your project. + /// + /// Attached to the event payload. + final String projectId; + + /// The DSN URI. + final Uri? uri; + + Uri get postUri { + final uriCopy = uri!; + final port = uriCopy.hasPort && + ((uriCopy.scheme == 'http' && uriCopy.port != 80) || + (uriCopy.scheme == 'https' && uriCopy.port != 443)) + ? ':${uriCopy.port}' + : ''; + + final pathLength = uriCopy.pathSegments.length; + + String apiPath; + if (pathLength > 1) { + // some paths would present before the projectID in the uri + apiPath = + (uriCopy.pathSegments.sublist(0, pathLength - 1) + ['api']).join('/'); + } else { + apiPath = 'api'; + } + return Uri.parse( + '${uriCopy.scheme}://${uriCopy.host}$port/$apiPath/$projectId/envelope/', + ); + } + + /// Parses a DSN String to a Dsn object + factory Dsn.parse(String dsn) { + final uri = Uri.parse(dsn); + final userInfo = uri.userInfo.split(':'); + + if (uri.pathSegments.isEmpty) { + throw ArgumentError( + 'Project ID not found in the URI path of the DSN URI: $dsn', + ); + } + + return Dsn( + publicKey: userInfo[0], + secretKey: userInfo.length >= 2 ? userInfo[1] : null, + projectId: uri.pathSegments.last, + uri: uri, + ); + } +} diff --git a/packages/dart/lib/src/protocol/max_body_size.dart b/packages/dart/lib/src/protocol/max_body_size.dart new file mode 100644 index 0000000000..448b021975 --- /dev/null +++ b/packages/dart/lib/src/protocol/max_body_size.dart @@ -0,0 +1,73 @@ +// See https://docs.sentry.io/platforms/dotnet/guides/aspnetcore/configuration/options/#max-request-body-size + +const _mediumSize = 10000; +const _smallSize = 4000; + +/// Describes the size of http request bodies which should be added to an event +enum MaxRequestBodySize { + /// Request bodies are never sent + never, + + /// Only small request bodies will be captured where the cutoff for small + /// depends on the SDK (typically 4KB) + small, + + /// Medium and small requests will be captured (typically 10KB) + medium, + + /// The SDK will always capture the request body for as long as Sentry can + /// make sense of it + always, +} + +extension MaxRequestBodySizeX on MaxRequestBodySize { + bool shouldAddBody(int contentLength) { + switch (this) { + case MaxRequestBodySize.never: + break; + case MaxRequestBodySize.small: + return contentLength <= _smallSize; + case MaxRequestBodySize.medium: + return contentLength <= _mediumSize; + case MaxRequestBodySize.always: + return true; + // No default here to get a warning when a new enum value is added. + } + return false; + } +} + +/// Describes the size of http response bodies which should be added to an event +/// This enum might be removed at any time. +enum MaxResponseBodySize { + /// Response bodies are never sent + never, + + /// Only small response bodies will be captured where the cutoff for small + /// depends on the SDK (typically 4KB) + small, + + /// Medium and small response will be captured (typically 10KB) + medium, + + /// The SDK will always capture the request body for as long as Sentry can + /// make sense of it + always, +} + +extension MaxResponseBodySizeX on MaxResponseBodySize { + bool shouldAddBody(int contentLength) { + switch (this) { + case MaxResponseBodySize.never: + break; + case MaxResponseBodySize.small: + return contentLength <= _smallSize; + case MaxResponseBodySize.medium: + return contentLength <= _mediumSize; + case MaxResponseBodySize.always: + return true; + // No default here to get a warning when a new enum value is added. + } + return false; + } +} diff --git a/packages/dart/lib/src/protocol/mechanism.dart b/packages/dart/lib/src/protocol/mechanism.dart new file mode 100644 index 0000000000..0ee37f7d16 --- /dev/null +++ b/packages/dart/lib/src/protocol/mechanism.dart @@ -0,0 +1,178 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +/// Sentry Exception Mechanism +/// The exception mechanism is an optional field residing +/// in the Exception Interface. It carries additional information about +/// the way the exception was created on the target system. +/// This includes general exception values obtained from operating system or +/// runtime APIs, as well as mechanism-specific values. +class Mechanism { + /// Required unique identifier of this mechanism determining rendering and processing of the mechanism data + /// The type attribute is required to send any exception mechanism attribute, + /// even if the SDK cannot determine the specific mechanism. + /// In this case, set the type to "generic". See below for an example. + String type; + + /// Optional human readable description of the error mechanism and a possible hint on how to solve this error + String? description; + + /// Optional fully qualified URL to an online help resource, possible interpolated with error parameters + String? helpLink; + + /// Optional flag indicating whether the exception has been handled by the user (e.g. via try..catch) + bool? handled; + + Map? _meta; + + /// Optional information from the operating system or runtime on the exception mechanism + /// The mechanism meta data usually carries error codes reported by + /// the runtime or operating system, along with a platform dependent + /// interpretation of these codes. SDKs can safely omit code names and + /// descriptions for well known error codes, as it will be filled out by + /// Sentry. For proprietary or vendor-specific error codes, + /// adding these values will give additional information to the user. + Map get meta => Map.unmodifiable(_meta ?? const {}); + + Map? _data; + + /// Arbitrary extra data that might help the user understand the error thrown by this mechanism + Map get data => Map.unmodifiable(_data ?? const {}); + + set data(Map? data) { + _data = (data != null) ? Map.from(data) : null; + } + + /// An optional flag indicating that this error is synthetic. + /// Synthetic errors are errors that carry little meaning by themselves. + /// This may be because they are created at a central place (like a crash handler), and are all called the same: Error, Segfault etc. When the flag is set, Sentry will then try to use other information (top in-app frame function) rather than exception type and value in the UI for the primary event display. This flag should be set for all "segfaults" for instance as every single error group would look very similar otherwise. + bool? synthetic; + + /// An optional boolean value, set `true` when the exception is the exception + /// group type specific to the platform or language. + /// The default is false when omitted. + /// For example, exceptions of type [PlatformException](https://api.flutter.dev/flutter/services/PlatformException-class.html) + /// have set it to `true`, others are set to `false`. + bool? isExceptionGroup; + + /// An optional string value describing the source of the exception. + /// + /// The SDK should populate this with the name of the property or attribute of + /// the parent exception that this exception was acquired from. + /// In the case of an array, it should include the zero-based array index as + /// well. + String? source; + + /// An optional numeric value providing an ID for the exception relative to + /// this specific event. + /// + /// The SDK should assign simple incrementing integers to each exception in + /// the tree, starting with 0 for the root of the tree. + /// In other words, when flattened into the list provided in the exception + /// values on the event, the last exception in the list should have ID 0, + /// the previous one should have ID 1, the next previous should have ID 2, etc. + int? exceptionId; + + /// An optional numeric value pointing at the [exceptionId] that is the parent + /// of this exception. + /// + /// The SDK should assign this to all exceptions except the root exception + /// (the last to be listed in the exception values). + int? parentId; + + @internal + final Map? unknown; + + Mechanism({ + required this.type, + this.description, + this.helpLink, + this.handled, + this.synthetic, + Map? meta, + Map? data, + this.isExceptionGroup, + this.source, + this.exceptionId, + this.parentId, + this.unknown, + }) : _meta = meta != null ? Map.from(meta) : null, + _data = data != null ? Map.from(data) : null; + + @Deprecated('Assign values directly to the instance.') + Mechanism copyWith({ + String? type, + String? description, + String? helpLink, + bool? handled, + Map? meta, + Map? data, + bool? synthetic, + bool? isExceptionGroup, + String? source, + int? exceptionId, + int? parentId, + }) => + Mechanism( + type: type ?? this.type, + description: description ?? this.description, + helpLink: helpLink ?? this.helpLink, + handled: handled ?? this.handled, + meta: meta ?? this.meta, + data: data ?? this.data, + synthetic: synthetic ?? this.synthetic, + isExceptionGroup: isExceptionGroup ?? this.isExceptionGroup, + source: source ?? this.source, + exceptionId: exceptionId ?? this.exceptionId, + parentId: parentId ?? this.parentId, + unknown: unknown, + ); + + /// Deserializes a [Mechanism] from JSON [Map]. + factory Mechanism.fromJson(Map jsonData) { + final json = AccessAwareMap(jsonData); + var data = json['data']; + if (data != null) { + data = Map.from(data as Map); + } + + var meta = json['meta']; + if (meta != null) { + meta = Map.from(meta as Map); + } + + return Mechanism( + type: json['type'], + description: json['description'], + helpLink: json['help_link'], + handled: json['handled'], + meta: meta, + data: data, + synthetic: json['synthetic'], + isExceptionGroup: json['is_exception_group'], + source: json['source'], + exceptionId: json['exception_id'], + parentId: json['parent_id'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + 'type': type, + if (description != null) 'description': description, + if (helpLink != null) 'help_link': helpLink, + if (handled != null) 'handled': handled, + if (_meta?.isNotEmpty ?? false) 'meta': _meta, + if (_data?.isNotEmpty ?? false) 'data': _data, + if (synthetic != null) 'synthetic': synthetic, + if (isExceptionGroup != null) 'is_exception_group': isExceptionGroup, + if (source != null) 'source': source, + if (exceptionId != null) 'exception_id': exceptionId, + if (parentId != null) 'parent_id': parentId, + }; + } +} diff --git a/packages/dart/lib/src/protocol/sdk_info.dart b/packages/dart/lib/src/protocol/sdk_info.dart new file mode 100644 index 0000000000..7903f910d3 --- /dev/null +++ b/packages/dart/lib/src/protocol/sdk_info.dart @@ -0,0 +1,60 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +/// An object describing the system SDK. +class SdkInfo { + String? sdkName; + int? versionMajor; + int? versionMinor; + int? versionPatchlevel; + + @internal + final Map? unknown; + + SdkInfo({ + this.sdkName, + this.versionMajor, + this.versionMinor, + this.versionPatchlevel, + this.unknown, + }); + + /// Deserializes a [SdkInfo] from JSON [Map]. + factory SdkInfo.fromJson(Map data) { + final json = AccessAwareMap(data); + return SdkInfo( + sdkName: json['sdk_name'], + versionMajor: json['version_major'], + versionMinor: json['version_minor'], + versionPatchlevel: json['version_patchlevel'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + if (sdkName != null) 'sdk_name': sdkName, + if (versionMajor != null) 'version_major': versionMajor, + if (versionMinor != null) 'version_minor': versionMinor, + if (versionPatchlevel != null) 'version_patchlevel': versionPatchlevel, + }; + } + + @Deprecated('Assign values directly to the instance.') + SdkInfo copyWith({ + String? sdkName, + int? versionMajor, + int? versionMinor, + int? versionPatchlevel, + }) => + SdkInfo( + sdkName: sdkName ?? this.sdkName, + versionMajor: versionMajor ?? this.versionMajor, + versionMinor: versionMinor ?? this.versionMinor, + versionPatchlevel: versionPatchlevel ?? this.versionPatchlevel, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/sdk_version.dart b/packages/dart/lib/src/protocol/sdk_version.dart new file mode 100644 index 0000000000..b80483accd --- /dev/null +++ b/packages/dart/lib/src/protocol/sdk_version.dart @@ -0,0 +1,131 @@ +import 'package:meta/meta.dart'; + +import 'sentry_package.dart'; +import 'access_aware_map.dart'; + +/// Describes the SDK that is submitting events to Sentry. +/// +/// https://develop.sentry.dev/sdk/event-payloads/sdk/ +/// +/// SDK's maintained by Sentry take the following format: +/// sentry.lang and for specializations: sentry.lang.specialization +/// +/// Examples: sentry.dart, sentry.dart.browser, sentry.dart.flutter +/// +/// It can also contain the packages bundled and integrations enabled. +/// +/// ``` +/// "sdk": { +/// "name": "sentry.dart.flutter", +/// "version": "5.0.0", +/// "integrations": [ +/// "tracing" +/// ], +/// "packages": [ +/// { +/// "name": "git:https://github.com/getsentry/sentry-cocoa.git", +/// "version": "5.1.0" +/// }, +/// { +/// "name": "maven:io.sentry.android", +/// "version": "2.2.0" +/// } +/// ] +/// } +/// ``` +class SdkVersion { + /// Creates an [SdkVersion] object which represents the SDK that created an [Event]. + SdkVersion({ + required this.name, + required this.version, + List? integrations, + List? packages, + this.unknown, + }) : + // List.from prevents from having immutable lists + _integrations = List.from(integrations ?? []), + _packages = List.from(packages ?? []); + + /// The name of the SDK. + String name; + + /// The version of the SDK. + String version; + + List _integrations; + + /// An immutable list of integrations enabled in the SDK that created the [Event]. + List get integrations => List.unmodifiable(_integrations); + + List _packages; + + /// An immutable list of packages that compose this SDK. + List get packages => List.unmodifiable(_packages); + + @internal + final Map? unknown; + + /// Deserializes a [SdkVersion] from JSON [Map]. + factory SdkVersion.fromJson(Map data) { + final json = AccessAwareMap(data); + final packagesJson = json['packages'] as List?; + final integrationsJson = json['integrations'] as List?; + + return SdkVersion( + name: json['name'], + version: json['version'], + packages: packagesJson + ?.map((e) => SentryPackage.fromJson(e as Map)) + .toList(), + integrations: integrationsJson?.map((e) => e as String).toList(), + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + 'name': name, + 'version': version, + if (packages.isNotEmpty) + 'packages': packages.map((p) => p.toJson()).toList(growable: false), + if (integrations.isNotEmpty) 'integrations': integrations, + }; + } + + /// Adds a package + void addPackage(String name, String version) { + for (final item in _packages) { + if (item.name == name && item.version == version) { + return; + } + } + + final package = SentryPackage(name, version); + _packages.add(package); + } + + // Adds an integration if not already added + void addIntegration(String integration) { + if (_integrations.contains(integration)) { + return; + } + _integrations.add(integration); + } + + @Deprecated('Assign values directly to the instance.') + SdkVersion copyWith({ + String? name, + String? version, + List? integrations, + List? packages, + }) => + SdkVersion( + name: name ?? this.name, + version: version ?? this.version, + integrations: integrations ?? _integrations, + packages: packages ?? _packages, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_app.dart b/packages/dart/lib/src/protocol/sentry_app.dart new file mode 100644 index 0000000000..6495ff38a0 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_app.dart @@ -0,0 +1,148 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +/// App context describes the application. +/// +/// As opposed to the runtime, this is the actual application that was +/// running and carries metadata about the current session. +class SentryApp { + static const type = 'app'; + + SentryApp({ + this.name, + this.version, + this.identifier, + this.build, + this.buildType, + this.startTime, + this.deviceAppHash, + this.appMemory, + this.inForeground, + this.viewNames, + this.textScale, + this.unknown, + }); + + /// Human readable application name, as it appears on the platform. + String? name; + + /// Human readable application version, as it appears on the platform. + String? version; + + /// Version-independent application identifier, often a dotted bundle ID. + String? identifier; + + /// Internal build identifier, as it appears on the platform. + String? build; + + /// String identifying the kind of build, e.g. `testflight`. + String? buildType; + + /// When the application was started by the user. + DateTime? startTime; + + /// Application specific device identifier. + String? deviceAppHash; + + /// Amount of memory used by the application in bytes. + int? appMemory; + + /// A flag indicating whether the app is in foreground or not. + /// An app is in foreground when it's visible to the user. + bool? inForeground; + + /// The names of the currently visible views. + List? viewNames; + + /// The current text scale. Only available on Flutter. + double? textScale; + + @internal + final Map? unknown; + + /// Deserializes a [SentryApp] from JSON [Map]. + factory SentryApp.fromJson(Map data) { + final json = AccessAwareMap(data); + final viewNamesJson = json['view_names'] as List?; + return SentryApp( + name: json['app_name'], + version: json['app_version'], + identifier: json['app_identifier'], + build: json['app_build'], + buildType: json['build_type'], + startTime: json['app_start_time'] != null + ? DateTime.tryParse(json['app_start_time']) + : null, + deviceAppHash: json['device_app_hash'], + appMemory: json['app_memory'], + inForeground: json['in_foreground'], + viewNames: viewNamesJson?.map((e) => e as String).toList(), + textScale: json['text_scale'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + if (name != null) 'app_name': name!, + if (version != null) 'app_version': version!, + if (identifier != null) 'app_identifier': identifier!, + if (build != null) 'app_build': build!, + if (buildType != null) 'build_type': buildType!, + if (startTime != null) 'app_start_time': startTime!.toIso8601String(), + if (deviceAppHash != null) 'device_app_hash': deviceAppHash!, + if (appMemory != null) 'app_memory': appMemory!, + if (inForeground != null) 'in_foreground': inForeground!, + if (viewNames != null && viewNames!.isNotEmpty) 'view_names': viewNames!, + if (textScale != null) 'text_scale': textScale!, + }; + } + + @Deprecated('Will be removed in a future version.') + SentryApp clone() => SentryApp( + name: name, + version: version, + identifier: identifier, + build: build, + buildType: buildType, + startTime: startTime, + deviceAppHash: deviceAppHash, + appMemory: appMemory, + inForeground: inForeground, + viewNames: viewNames, + textScale: textScale, + unknown: unknown, + ); + + @Deprecated('Assign values directly to the instance.') + SentryApp copyWith({ + String? name, + String? version, + String? identifier, + String? build, + String? buildType, + DateTime? startTime, + String? deviceAppHash, + int? appMemory, + bool? inForeground, + List? viewNames, + double? textScale, + }) => + SentryApp( + name: name ?? this.name, + version: version ?? this.version, + identifier: identifier ?? this.identifier, + build: build ?? this.build, + buildType: buildType ?? this.buildType, + startTime: startTime ?? this.startTime, + deviceAppHash: deviceAppHash ?? this.deviceAppHash, + appMemory: appMemory ?? this.appMemory, + inForeground: inForeground ?? this.inForeground, + viewNames: viewNames ?? this.viewNames, + textScale: textScale ?? this.textScale, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_baggage_header.dart b/packages/dart/lib/src/protocol/sentry_baggage_header.dart new file mode 100644 index 0000000000..3d7007030a --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_baggage_header.dart @@ -0,0 +1,18 @@ +import 'package:meta/meta.dart'; + +import '../sentry_baggage.dart'; + +@immutable +class SentryBaggageHeader { + static const _traceHeader = 'baggage'; + + SentryBaggageHeader(this.value); + + final String value; + + String get name => _traceHeader; + + factory SentryBaggageHeader.fromBaggage(SentryBaggage baggage) { + return SentryBaggageHeader(baggage.toHeaderString()); + } +} diff --git a/packages/dart/lib/src/protocol/sentry_browser.dart b/packages/dart/lib/src/protocol/sentry_browser.dart new file mode 100644 index 0000000000..9ba214dac9 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_browser.dart @@ -0,0 +1,60 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +/// Carries information about the browser or user agent for web-related errors. +/// +/// This can either be the browser this event ocurred in, or the user +/// agent of a web request that triggered the event. +class SentryBrowser { + static const type = 'browser'; + + /// Creates an instance of [SentryBrowser]. + SentryBrowser({this.name, this.version, this.unknown}); + + /// Human readable application name, as it appears on the platform. + String? name; + + /// Human readable application version, as it appears on the platform. + String? version; + + @internal + final Map? unknown; + + /// Deserializes a [SentryBrowser] from JSON [Map]. + factory SentryBrowser.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryBrowser( + name: json['name'], + version: json['version'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + if (name != null) 'name': name, + if (version != null) 'version': version, + }; + } + + @Deprecated('Will be removed in a future version.') + SentryBrowser clone() => SentryBrowser( + name: name, + version: version, + unknown: unknown, + ); + + @Deprecated('Assign values directly to the instance.') + SentryBrowser copyWith({ + String? name, + String? version, + }) => + SentryBrowser( + name: name ?? this.name, + version: version ?? this.version, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_culture.dart b/packages/dart/lib/src/protocol/sentry_culture.dart new file mode 100644 index 0000000000..300f697afc --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_culture.dart @@ -0,0 +1,89 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +/// Culture Context describes certain properties of the culture in which the +/// software is used. +class SentryCulture { + static const type = 'culture'; + + SentryCulture({ + this.calendar, + this.displayName, + this.locale, + this.is24HourFormat, + this.timezone, + this.unknown, + }); + + factory SentryCulture.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryCulture( + calendar: json['calendar'], + displayName: json['display_name'], + locale: json['locale'], + is24HourFormat: json['is_24_hour_format'], + timezone: json['timezone'], + unknown: json.notAccessed(), + ); + } + + /// Optional: For example `GregorianCalendar`. Free form string. + String? calendar; + + /// Optional: Human readable name of the culture. + /// For example `English (United States)` + String? displayName; + + /// Optional. The name identifier, usually following the RFC 4646. + /// For example `en-US` or `pt-BR`. + String? locale; + + /// Optional. boolean, either true or false. + bool? is24HourFormat; + + /// Optional. The timezone of the locale. For example, `Europe/Vienna`. + String? timezone; + + @internal + final Map? unknown; + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + if (calendar != null) 'calendar': calendar!, + if (displayName != null) 'display_name': displayName!, + if (locale != null) 'locale': locale!, + if (is24HourFormat != null) 'is_24_hour_format': is24HourFormat!, + if (timezone != null) 'timezone': timezone!, + }; + } + + @Deprecated('Will be removed in a future version.') + SentryCulture clone() => SentryCulture( + calendar: calendar, + displayName: displayName, + locale: locale, + is24HourFormat: is24HourFormat, + timezone: timezone, + unknown: unknown, + ); + + @Deprecated('Assign values directly to the instance.') + SentryCulture copyWith({ + String? calendar, + String? displayName, + String? locale, + bool? is24HourFormat, + String? timezone, + }) => + SentryCulture( + calendar: calendar ?? this.calendar, + displayName: displayName ?? this.displayName, + locale: locale ?? this.locale, + is24HourFormat: is24HourFormat ?? this.is24HourFormat, + timezone: timezone ?? this.timezone, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_device.dart b/packages/dart/lib/src/protocol/sentry_device.dart new file mode 100644 index 0000000000..8af83f61c5 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_device.dart @@ -0,0 +1,401 @@ +import 'package:meta/meta.dart'; +import '../sentry_options.dart'; +import 'access_aware_map.dart'; + +/// If a device is on portrait or landscape mode +enum SentryOrientation { portrait, landscape } + +/// This describes the device that caused the event. +class SentryDevice { + static const type = 'device'; + + SentryDevice({ + this.name, + this.family, + this.model, + this.modelId, + this.arch, + this.batteryLevel, + this.orientation, + this.manufacturer, + this.brand, + this.screenHeightPixels, + this.screenWidthPixels, + this.screenDensity, + this.screenDpi, + this.online, + this.charging, + this.lowMemory, + this.simulator, + this.memorySize, + this.freeMemory, + this.usableMemory, + this.storageSize, + this.freeStorage, + this.externalStorageSize, + this.externalFreeStorage, + this.bootTime, + this.processorCount, + this.cpuDescription, + this.processorFrequency, + this.deviceType, + this.batteryStatus, + this.deviceUniqueIdentifier, + this.supportsVibration, + this.supportsAccelerometer, + this.supportsGyroscope, + this.supportsAudio, + this.supportsLocationService, + this.unknown, + }) : assert( + batteryLevel == null || (batteryLevel >= 0 && batteryLevel <= 100), + ); + + /// The name of the device. This is typically a hostname. + String? name; + + /// The family of the device. + /// + /// This is normally the common part of model names across generations. + /// For instance `iPhone` would be a reasonable family, + /// so would be `Samsung Galaxy`. + String? family; + + /// The model name. This for instance can be `Samsung Galaxy S3`. + String? model; + + /// An internal hardware revision to identify the device exactly. + String? modelId; + + /// The CPU architecture. + String? arch; + + /// If the device has a battery, this can be an floating point value + /// defining the battery level (in the range 0-100). + double? batteryLevel; + + /// Defines the orientation of a device. + SentryOrientation? orientation; + + /// The manufacturer of the device. + String? manufacturer; + + /// The brand of the device. + String? brand; + + /// The screen height in pixels. (e.g.: `600`, `1080`). + int? screenHeightPixels; + + /// The screen width in pixels. (e.g.: `800`, `1920`). + int? screenWidthPixels; + + /// A floating point denoting the screen density. + double? screenDensity; + + /// A decimal value reflecting the DPI (dots-per-inch) density. + int? screenDpi; + + /// Whether the device was online or not. + bool? online; + + /// Whether the device was charging or not. + bool? charging; + + /// Whether the device was low on memory. + bool? lowMemory; + + /// A flag indicating whether this device is a simulator or an actual device. + bool? simulator; + + /// Total system memory available in bytes. + int? memorySize; + + /// Free system memory in bytes. + int? freeMemory; + + /// Memory usable for the app in bytes. + int? usableMemory; + + /// Total device storage in bytes. + int? storageSize; + + /// Free device storage in bytes. + int? freeStorage; + + /// Total size of an attached external storage in bytes + /// (e.g.: android SDK card). + int? externalStorageSize; + + /// Free size of an attached external storage in bytes + /// (e.g.: android SDK card). + int? externalFreeStorage; + + /// When the system was booted + DateTime? bootTime; + + /// Optional. Number of "logical processors". For example, `8`. + int? processorCount; + + /// Optional. CPU description. For example, `Intel(R) Core(TM)2 Quad CPU Q6600 @ 2.40GHz`. + String? cpuDescription; + + /// Optional. Processor frequency in MHz. Note that the actual CPU frequency + /// might vary depending on current load and power conditions, + /// especially on low-powered devices like phones and laptops. + double? processorFrequency; + + /// Optional. Kind of device the application is running on. + /// For example, `Unknown`, `Handheld`, `Console`, `Desktop`. + String? deviceType; + + /// Optional. Status of the device's battery. + /// For example, `Unknown`, `Charging`, `Discharging`, `NotCharging`, `Full`. + String? batteryStatus; + + /// Optional. Unique device identifier. + /// This value might only be used if [SentryOptions.sendDefaultPii] + /// is enabled. + String? deviceUniqueIdentifier; + + /// Optional. Is vibration available on the device? + bool? supportsVibration; + + /// Optional. Is accelerometer available on the device? + bool? supportsAccelerometer; + + /// Optional. Is gyroscope available on the device? + bool? supportsGyroscope; + + /// Optional. Is audio available on the device? + bool? supportsAudio; + + /// Optional. Is the device capable of reporting its location? + bool? supportsLocationService; + + @internal + final Map? unknown; + + /// Deserializes a [SentryDevice] from JSON [Map]. + factory SentryDevice.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryDevice( + name: json['name'], + family: json['family'], + model: json['model'], + modelId: json['model_id'], + arch: json['arch'], + batteryLevel: + (json['battery_level'] is num ? json['battery_level'] as num : null) + ?.toDouble(), + orientation: json['orientation'] == 'portrait' + ? SentryOrientation.portrait + : json['orientation'] == 'landscape' + ? SentryOrientation.landscape + : null, + manufacturer: json['manufacturer'], + brand: json['brand'], + screenHeightPixels: json['screen_height_pixels']?.toInt(), + screenWidthPixels: json['screen_width_pixels']?.toInt(), + screenDensity: json['screen_density'], + screenDpi: json['screen_dpi'], + online: json['online'], + charging: json['charging'], + lowMemory: json['low_memory'], + simulator: json['simulator'], + memorySize: json['memory_size'], + freeMemory: json['free_memory'], + usableMemory: json['usable_memory'], + storageSize: json['storage_size'], + freeStorage: json['free_storage'], + externalStorageSize: json['external_storage_size'], + externalFreeStorage: json['external_free_storage'], + bootTime: json['boot_time'] != null + ? DateTime.tryParse(json['boot_time']) + : null, + processorCount: json['processor_count'], + cpuDescription: json['cpu_description'], + processorFrequency: json['processor_frequency'], + deviceType: json['device_type'], + batteryStatus: json['battery_status'], + deviceUniqueIdentifier: json['device_unique_identifier'], + supportsVibration: json['supports_vibration'], + supportsAccelerometer: json['supports_accelerometer'], + supportsGyroscope: json['supports_gyroscope'], + supportsAudio: json['supports_audio'], + supportsLocationService: json['supports_location_service'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + if (name != null) 'name': name, + if (family != null) 'family': family, + if (model != null) 'model': model, + if (modelId != null) 'model_id': modelId, + if (arch != null) 'arch': arch, + if (batteryLevel != null) 'battery_level': batteryLevel, + if (orientation != null) 'orientation': orientation!.name, + if (manufacturer != null) 'manufacturer': manufacturer, + if (brand != null) 'brand': brand, + if (screenWidthPixels != null) 'screen_width_pixels': screenWidthPixels, + if (screenHeightPixels != null) + 'screen_height_pixels': screenHeightPixels, + if (screenDensity != null) 'screen_density': screenDensity, + if (screenDpi != null) 'screen_dpi': screenDpi, + if (online != null) 'online': online, + if (charging != null) 'charging': charging, + if (lowMemory != null) 'low_memory': lowMemory, + if (simulator != null) 'simulator': simulator, + if (memorySize != null) 'memory_size': memorySize, + if (freeMemory != null) 'free_memory': freeMemory, + if (usableMemory != null) 'usable_memory': usableMemory, + if (storageSize != null) 'storage_size': storageSize, + if (freeStorage != null) 'free_storage': freeStorage, + if (externalStorageSize != null) + 'external_storage_size': externalStorageSize, + if (externalFreeStorage != null) + 'external_free_storage': externalFreeStorage, + if (bootTime != null) 'boot_time': bootTime!.toIso8601String(), + if (processorCount != null) 'processor_count': processorCount, + if (cpuDescription != null) 'cpu_description': cpuDescription, + if (processorFrequency != null) 'processor_frequency': processorFrequency, + if (deviceType != null) 'device_type': deviceType, + if (batteryStatus != null) 'battery_status': batteryStatus, + if (deviceUniqueIdentifier != null) + 'device_unique_identifier': deviceUniqueIdentifier, + if (supportsVibration != null) 'supports_vibration': supportsVibration, + if (supportsAccelerometer != null) + 'supports_accelerometer': supportsAccelerometer, + if (supportsGyroscope != null) 'supports_gyroscope': supportsGyroscope, + if (supportsAudio != null) 'supports_audio': supportsAudio, + if (supportsLocationService != null) + 'supports_location_service': supportsLocationService, + }; + } + + @Deprecated('Will be removed in a future version.') + SentryDevice clone() => SentryDevice( + name: name, + family: family, + model: model, + modelId: modelId, + arch: arch, + batteryLevel: batteryLevel, + orientation: orientation, + manufacturer: manufacturer, + brand: brand, + screenHeightPixels: screenHeightPixels, + screenWidthPixels: screenWidthPixels, + screenDensity: screenDensity, + screenDpi: screenDpi, + online: online, + charging: charging, + lowMemory: lowMemory, + simulator: simulator, + memorySize: memorySize, + freeMemory: freeMemory, + usableMemory: usableMemory, + storageSize: storageSize, + freeStorage: freeStorage, + externalStorageSize: externalStorageSize, + externalFreeStorage: externalFreeStorage, + bootTime: bootTime, + processorCount: processorCount, + cpuDescription: cpuDescription, + processorFrequency: processorFrequency, + deviceType: deviceType, + batteryStatus: batteryStatus, + deviceUniqueIdentifier: deviceUniqueIdentifier, + supportsVibration: supportsVibration, + supportsAccelerometer: supportsAccelerometer, + supportsGyroscope: supportsGyroscope, + supportsAudio: supportsAudio, + supportsLocationService: supportsLocationService, + unknown: unknown, + ); + + @Deprecated('Assign values directly to the instance.') + SentryDevice copyWith({ + String? name, + String? family, + String? model, + String? modelId, + String? arch, + double? batteryLevel, + SentryOrientation? orientation, + String? manufacturer, + String? brand, + int? screenHeightPixels, + int? screenWidthPixels, + double? screenDensity, + int? screenDpi, + bool? online, + bool? charging, + bool? lowMemory, + bool? simulator, + int? memorySize, + int? freeMemory, + int? usableMemory, + int? storageSize, + int? freeStorage, + int? externalStorageSize, + int? externalFreeStorage, + DateTime? bootTime, + int? processorCount, + String? cpuDescription, + double? processorFrequency, + String? deviceType, + String? batteryStatus, + String? deviceUniqueIdentifier, + bool? supportsVibration, + bool? supportsAccelerometer, + bool? supportsGyroscope, + bool? supportsAudio, + bool? supportsLocationService, + }) => + SentryDevice( + name: name ?? this.name, + family: family ?? this.family, + model: model ?? this.model, + modelId: modelId ?? this.modelId, + arch: arch ?? this.arch, + batteryLevel: batteryLevel ?? this.batteryLevel, + orientation: orientation ?? this.orientation, + manufacturer: manufacturer ?? this.manufacturer, + brand: brand ?? this.brand, + screenHeightPixels: screenHeightPixels ?? this.screenHeightPixels, + screenWidthPixels: screenWidthPixels ?? this.screenWidthPixels, + screenDensity: screenDensity ?? this.screenDensity, + screenDpi: screenDpi ?? this.screenDpi, + online: online ?? this.online, + charging: charging ?? this.charging, + lowMemory: lowMemory ?? this.lowMemory, + simulator: simulator ?? this.simulator, + memorySize: memorySize ?? this.memorySize, + freeMemory: freeMemory ?? this.freeMemory, + usableMemory: usableMemory ?? this.usableMemory, + storageSize: storageSize ?? this.storageSize, + freeStorage: freeStorage ?? this.freeStorage, + externalStorageSize: externalStorageSize ?? this.externalStorageSize, + externalFreeStorage: externalFreeStorage ?? this.externalFreeStorage, + bootTime: bootTime ?? this.bootTime, + processorCount: processorCount ?? this.processorCount, + cpuDescription: cpuDescription ?? this.cpuDescription, + processorFrequency: processorFrequency ?? this.processorFrequency, + deviceType: deviceType ?? this.deviceType, + batteryStatus: batteryStatus ?? this.batteryStatus, + deviceUniqueIdentifier: + deviceUniqueIdentifier ?? this.deviceUniqueIdentifier, + supportsVibration: supportsVibration ?? this.supportsVibration, + supportsAccelerometer: + supportsAccelerometer ?? this.supportsAccelerometer, + supportsGyroscope: supportsGyroscope ?? this.supportsGyroscope, + supportsAudio: supportsAudio ?? this.supportsAudio, + supportsLocationService: + supportsLocationService ?? this.supportsLocationService, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_event.dart b/packages/dart/lib/src/protocol/sentry_event.dart new file mode 100644 index 0000000000..61b68decbd --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_event.dart @@ -0,0 +1,420 @@ +import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; + +import '../protocol.dart'; +import '../throwable_mechanism.dart'; +import '../utils.dart'; +import 'access_aware_map.dart'; + +/// An event to be reported to Sentry.io. +class SentryEvent with SentryEventLike { + /// Creates an event. + SentryEvent({ + SentryId? eventId, + DateTime? timestamp, + Map? modules, + Map? tags, + @Deprecated( + 'Additional Data is deprecated in favor of structured [Contexts] and should be avoided when possible') + Map? extra, + List? fingerprint, + List? breadcrumbs, + List? exceptions, + List? threads, + this.sdk, + this.platform, + this.logger, + this.serverName, + this.release, + this.dist, + this.environment, + this.message, + this.transaction, + dynamic throwable, + this.level, + this.culprit, + this.user, + Contexts? contexts, + this.request, + this.debugMeta, + this.type, + this.unknown, + }) : eventId = eventId ?? SentryId.newId(), + timestamp = timestamp ?? getUtcDateTime(), + contexts = contexts ?? Contexts(), + modules = modules != null ? Map.from(modules) : null, + tags = tags != null ? Map.from(tags) : null, + // ignore: deprecated_member_use_from_same_package + extra = extra != null ? Map.from(extra) : null, + fingerprint = fingerprint != null ? List.from(fingerprint) : null, + breadcrumbs = breadcrumbs != null ? List.from(breadcrumbs) : null, + exceptions = exceptions != null ? List.from(exceptions) : null, + threads = threads != null ? List.from(threads) : null, + _throwable = throwable; + + /// Refers to the default fingerprinting algorithm. + /// + /// You do not need to specify this value unless you supplement the default + /// fingerprint with custom fingerprints. + static const String defaultFingerprint = '{{ default }}'; + + /// The ID Sentry.io assigned to the submitted event for future reference. + SentryId eventId; + + /// A timestamp representing when the event occurred. + DateTime? timestamp; + + /// A string representing the platform the SDK is submitting from. This will be used by the Sentry interface to customize various components in the interface. + String? platform; + + /// The logger that logged the event. + String? logger; + + /// Identifies the server that logged this event. + String? serverName; + + /// The version of the application that logged the event. + String? release; + + /// The distribution of the application. + String? dist; + + /// The environment that logged the event, e.g. "production", "staging". + String? environment; + + /// A list of relevant modules and their versions. + Map? modules; + + /// Event message. + /// + /// Generally an event either contains a [message] or [exceptions]. + SentryMessage? message; + + dynamic _throwable; + + /// An object that was thrown. + /// + /// It's `runtimeType` and `toString()` are logged. + /// If it's an Error, with a stackTrace, the stackTrace is logged. + /// If this behavior is undesirable, consider using a custom formatted + /// [message] instead. + dynamic get throwable => + (_throwable is ThrowableMechanism) ? _throwable.throwable : _throwable; + + /// A throwable decorator that holds a [Mechanism] related to the decorated + /// [throwable] + /// + /// Use the [throwable] directly if you don't want the decorated throwable + dynamic get throwableMechanism => _throwable; + + /// One or multiple chained (nested) exceptions that occurred in a program. + List? exceptions; + + /// The Threads Interface specifies threads that were running at the time an + /// event happened. These threads can also contain stack traces. + /// Typically not needed in Dart applications. + List? threads; + + /// The name of the transaction which generated this event, + /// for example, the route name: `"/users//"`. + String? transaction; + + /// How important this event is. + SentryLevel? level; + + /// What caused this event to be logged. + String? culprit; + + /// Name/value pairs that events can be searched by. + Map? tags; + + /// Arbitrary name/value pairs attached to the event. + /// + /// Sentry.io docs do not talk about restrictions on the values, other than + /// they must be JSON-serializable. + @Deprecated( + 'Additional Data is deprecated in favor of structured [Contexts] and should be avoided when possible') + Map? extra; + + /// List of breadcrumbs for this event. + /// + /// See also: + /// * https://docs.sentry.io/platforms/dart/enriching-events/breadcrumbs/ + /// * https://docs.sentry.io/platforms/flutter/enriching-events/breadcrumbs/ + List? breadcrumbs; + + /// Information about the current user. + /// + /// The value in this field overrides the user context + /// set in [Scope.user] for this logged event. + SentryUser? user; + + /// The context interfaces provide additional context data. + /// Typically this is data related to the current user, + /// the current HTTP request. + Contexts contexts; + + /// Used to deduplicate events by grouping ones with the same fingerprint + /// together. + /// + /// If not specified a default deduplication fingerprint is used. The default + /// fingerprint may be supplemented by additional fingerprints by specifying + /// multiple values. The default fingerprint can be specified by adding + /// [defaultFingerprint] to the list in addition to your custom values. + /// + /// Examples: + /// ```dart + /// // A completely custom fingerprint: + /// var custom = ['foo', 'bar', 'baz']; + /// // A fingerprint that supplements the default one with value 'foo': + /// var supplemented = [SentryEvent.defaultFingerprint, 'foo']; + /// ``` + List? fingerprint; + + /// The SDK Interface describes the Sentry SDK and its configuration used + /// to capture and transmit an event. + SdkVersion? sdk; + + /// Contains information on a HTTP request related to the event. + /// In client, this can be an outgoing request, or the request that rendered + /// the current web page. + /// On server, this could be the incoming web request that is being handled + SentryRequest? request; + + /// The debug meta interface carries debug information for processing errors + /// and crash reports. + DebugMeta? debugMeta; + + /// The event type determines how Sentry handles the event and has an impact + /// on processing, rate limiting, and quotas. + /// defaults to 'default' + String? type; + + @internal + final Map? unknown; + + @Deprecated('Assign values directly to the instance.') + @override + SentryEvent copyWith({ + SentryId? eventId, + DateTime? timestamp, + String? platform, + String? logger, + String? serverName, + String? release, + String? dist, + String? environment, + Map? modules, + SentryMessage? message, + String? transaction, + dynamic throwable, + SentryLevel? level, + String? culprit, + Map? tags, + @Deprecated( + 'Additional Data is deprecated in favor of structured [Contexts] and should be avoided when possible') + Map? extra, + List? fingerprint, + SentryUser? user, + Contexts? contexts, + List? breadcrumbs, + SdkVersion? sdk, + SentryRequest? request, + DebugMeta? debugMeta, + List? exceptions, + List? threads, + String? type, + }) => + SentryEvent( + eventId: eventId ?? this.eventId, + timestamp: timestamp ?? this.timestamp, + platform: platform ?? this.platform, + logger: logger ?? this.logger, + serverName: serverName ?? this.serverName, + release: release ?? this.release, + dist: dist ?? this.dist, + environment: environment ?? this.environment, + modules: (modules != null ? Map.from(modules) : null) ?? this.modules, + message: message ?? this.message, + transaction: transaction ?? this.transaction, + throwable: throwable ?? _throwable, + level: level ?? this.level, + culprit: culprit ?? this.culprit, + tags: (tags != null ? Map.from(tags) : null) ?? this.tags, + // ignore: deprecated_member_use_from_same_package + extra: (extra != null ? Map.from(extra) : null) ?? this.extra, + fingerprint: (fingerprint != null ? List.from(fingerprint) : null) ?? + this.fingerprint, + user: user ?? this.user, + contexts: contexts ?? this.contexts, + breadcrumbs: (breadcrumbs != null ? List.from(breadcrumbs) : null) ?? + this.breadcrumbs, + sdk: sdk ?? this.sdk, + request: request ?? this.request, + debugMeta: debugMeta ?? this.debugMeta, + exceptions: (exceptions != null ? List.from(exceptions) : null) ?? + this.exceptions, + threads: (threads != null ? List.from(threads) : null) ?? this.threads, + type: type ?? this.type, + unknown: unknown, + ); + + /// Deserializes a [SentryEvent] from JSON [Map]. + factory SentryEvent.fromJson(Map data) { + final json = AccessAwareMap(data); + + final breadcrumbsJson = json['breadcrumbs'] as List?; + final breadcrumbs = breadcrumbsJson + ?.map((e) => Breadcrumb.fromJson(e)) + .toList(growable: false); + + final threadValues = json['threads']?['values'] as List?; + final threads = threadValues + ?.map((e) => SentryThread.fromJson(e)) + .toList(growable: false); + + final exceptionValues = json['exception']?['values'] as List?; + final exceptions = exceptionValues + ?.map((e) => SentryException.fromJson(e)) + .toList(growable: false); + + final modules = json['modules']?.cast(); + final tags = json['tags']?.cast(); + + final timestampJson = json['timestamp']; + final levelJson = json['level']; + final fingerprintJson = json['fingerprint'] as List?; + final sdkVersionJson = json['sdk'] as Map?; + final messageJson = json['message'] as Map?; + final userJson = json['user'] as Map?; + final contextsJson = json['contexts'] as Map?; + final requestJson = json['request'] as Map?; + final debugMetaJson = json['debug_meta'] as Map?; + + var extra = json['extra']; + if (extra != null) { + extra = Map.from(extra as Map); + } + + return SentryEvent( + eventId: SentryId.fromId(json['event_id']), + timestamp: + timestampJson != null ? DateTime.tryParse(timestampJson) : null, + modules: modules, + tags: tags, + // ignore: deprecated_member_use_from_same_package + extra: extra, + fingerprint: + fingerprintJson?.map((e) => e as String).toList(growable: false), + breadcrumbs: breadcrumbs, + sdk: sdkVersionJson != null && sdkVersionJson.isNotEmpty + ? SdkVersion.fromJson(sdkVersionJson) + : null, + platform: json['platform'], + logger: json['logger'], + serverName: json['server_name'], + release: json['release'], + dist: json['dist'], + environment: json['environment'], + message: messageJson != null && messageJson.isNotEmpty + ? SentryMessage.fromJson(messageJson) + : null, + transaction: json['transaction'], + threads: threads, + level: levelJson != null ? SentryLevel.fromName(levelJson) : null, + culprit: json['culprit'], + user: userJson != null && userJson.isNotEmpty + ? SentryUser.fromJson(userJson) + : null, + contexts: contextsJson != null && contextsJson.isNotEmpty + ? Contexts.fromJson(contextsJson) + : null, + request: requestJson != null && requestJson.isNotEmpty + ? SentryRequest.fromJson(requestJson) + : null, + debugMeta: debugMetaJson != null && debugMetaJson.isNotEmpty + ? DebugMeta.fromJson(debugMetaJson) + : null, + exceptions: exceptions, + type: json['type'], + unknown: json.notAccessed(), + ); + } + + /// Serializes this event to JSON. + Map toJson() { + var messageMap = message?.toJson(); + final contextsMap = contexts.toJson(); + final userMap = user?.toJson(); + final sdkMap = sdk?.toJson(); + final requestMap = request?.toJson(); + final debugMetaMap = debugMeta?.toJson(); + final exceptionsJson = exceptions + ?.map((e) => e.toJson()) + .where((e) => e.isNotEmpty) + .toList(growable: false); + + // Thread serialization is tricky: + // - Thread should not have a stacktrace when an exception is connected to it + // - Thread should serializae a stacktrace when no exception is connected to it + + // These are the thread ids with a connected exception + final threadIds = exceptions + ?.map((element) => element.threadId) + .where((element) => element != null) + .toSet(); + + final threadJson = threads + ?.map((element) { + if (threadIds?.contains(element.id) ?? false) { + // remove thread.stacktrace if a connected exception exists + final json = element.toJson(); + json.remove('stacktrace'); + return json; + } + return element.toJson(); + }) + .where((e) => e.isNotEmpty) + .toList(growable: false); + + return { + ...?unknown, + 'event_id': eventId.toString(), + if (timestamp != null) + 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp!), + if (platform != null) 'platform': platform, + if (logger != null) 'logger': logger, + if (serverName != null) 'server_name': serverName, + if (release != null) 'release': release, + if (dist != null) 'dist': dist, + if (environment != null) 'environment': environment, + if (modules != null && modules!.isNotEmpty) 'modules': modules, + if (transaction != null) 'transaction': transaction, + if (level != null) 'level': level!.name, + if (culprit != null) 'culprit': culprit, + if (tags?.isNotEmpty ?? false) 'tags': tags, + // ignore: deprecated_member_use_from_same_package + if (extra?.isNotEmpty ?? false) 'extra': extra, + if (type != null) 'type': type, + if (fingerprint?.isNotEmpty ?? false) 'fingerprint': fingerprint, + if (breadcrumbs?.isNotEmpty ?? false) + 'breadcrumbs': + breadcrumbs?.map((b) => b.toJson()).toList(growable: false), + if (messageMap?.isNotEmpty ?? false) 'message': messageMap, + if (contextsMap.isNotEmpty) 'contexts': contextsMap, + if (userMap?.isNotEmpty ?? false) 'user': userMap, + if (sdkMap?.isNotEmpty ?? false) 'sdk': sdkMap, + if (requestMap?.isNotEmpty ?? false) 'request': requestMap, + if (debugMetaMap?.isNotEmpty ?? false) 'debug_meta': debugMetaMap, + if (exceptionsJson?.isNotEmpty ?? false) + 'exception': {'values': exceptionsJson}, + if (threadJson?.isNotEmpty ?? false) 'threads': {'values': threadJson}, + }; + } + + // Returns first non-null stack trace of this event + @internal + SentryStackTrace? get stacktrace => + exceptions?.firstWhereOrNull((e) => e.stackTrace != null)?.stackTrace ?? + threads?.firstWhereOrNull((t) => t.stacktrace != null)?.stacktrace; +} diff --git a/packages/dart/lib/src/protocol/sentry_exception.dart b/packages/dart/lib/src/protocol/sentry_exception.dart new file mode 100644 index 0000000000..a27d86c140 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_exception.dart @@ -0,0 +1,112 @@ +import 'package:meta/meta.dart'; + +import '../protocol.dart'; +import 'access_aware_map.dart'; + +/// The Exception Interface specifies an exception or error that occurred in a program. +class SentryException { + /// Required. The type of exception + String? type; + + /// Required. The value of the exception + String? value; + + /// The optional module, or package which the exception type lives in. + String? module; + + /// An optional stack trace object + SentryStackTrace? stackTrace; + + /// An optional object describing the [Mechanism] that created this exception + Mechanism? mechanism; + + /// Represents a [SentryThread.id]. + int? threadId; + + dynamic throwable; + + @internal + Map? unknown; + + List? _exceptions; + + SentryException({ + required this.type, + required this.value, + this.module, + this.stackTrace, + this.mechanism, + this.threadId, + this.throwable, + this.unknown, + }); + + /// Deserializes a [SentryException] from JSON [Map]. + factory SentryException.fromJson(Map data) { + final json = AccessAwareMap(data); + + final stackTraceJson = json['stacktrace']; + final mechanismJson = json['mechanism']; + return SentryException( + type: json['type'], + value: json['value'], + module: json['module'], + stackTrace: stackTraceJson != null + ? SentryStackTrace.fromJson(stackTraceJson) + : null, + mechanism: + mechanismJson != null ? Mechanism.fromJson(mechanismJson) : null, + threadId: json['thread_id'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + if (type != null) 'type': type, + if (value != null) 'value': value, + if (module != null) 'module': module, + if (stackTrace != null) 'stacktrace': stackTrace!.toJson(), + if (mechanism != null) 'mechanism': mechanism!.toJson(), + if (threadId != null) 'thread_id': threadId, + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryException copyWith({ + String? type, + String? value, + String? module, + SentryStackTrace? stackTrace, + Mechanism? mechanism, + int? threadId, + dynamic throwable, + }) => + SentryException( + type: type ?? this.type, + value: value ?? this.value, + module: module ?? this.module, + stackTrace: stackTrace ?? this.stackTrace?.copyWith(), + mechanism: mechanism ?? this.mechanism?.copyWith(), + threadId: threadId ?? this.threadId, + throwable: throwable ?? this.throwable, + unknown: unknown, + ); + + @internal + List? get exceptions => + _exceptions != null ? List.unmodifiable(_exceptions!) : null; + + @internal + set exceptions(List? value) { + _exceptions = value; + } + + @internal + void addException(SentryException exception) { + _exceptions ??= []; + _exceptions!.add(exception); + } +} diff --git a/packages/dart/lib/src/protocol/sentry_feature_flag.dart b/packages/dart/lib/src/protocol/sentry_feature_flag.dart new file mode 100644 index 0000000000..9feb655424 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_feature_flag.dart @@ -0,0 +1,51 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +class SentryFeatureFlag { + final String flag; + final bool result; + + @internal + final Map? unknown; + + SentryFeatureFlag({ + required this.flag, + required this.result, + this.unknown, + }); + + factory SentryFeatureFlag.fromJson(Map data) { + final json = AccessAwareMap(data); + + return SentryFeatureFlag( + flag: json['flag'], + result: json['result'], + unknown: json.notAccessed(), + ); + } + + Map toJson() { + return { + ...?unknown, + 'flag': flag, + 'result': result, + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryFeatureFlag copyWith({ + String? flag, + bool? result, + Map? unknown, + }) { + return SentryFeatureFlag( + flag: flag ?? this.flag, + result: result ?? this.result, + unknown: unknown ?? this.unknown, + ); + } + + @Deprecated('Will be removed in a future version.') + SentryFeatureFlag clone() => copyWith(); +} diff --git a/packages/dart/lib/src/protocol/sentry_feature_flags.dart b/packages/dart/lib/src/protocol/sentry_feature_flags.dart new file mode 100644 index 0000000000..324f0bbbbd --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_feature_flags.dart @@ -0,0 +1,53 @@ +import 'package:meta/meta.dart'; +import 'sentry_feature_flag.dart'; +import 'access_aware_map.dart'; + +class SentryFeatureFlags { + static const type = 'flags'; + + List values; + + @internal + Map? unknown; + + SentryFeatureFlags({ + required this.values, + this.unknown, + }); + + factory SentryFeatureFlags.fromJson(Map data) { + final json = AccessAwareMap(data); + + final valuesValues = json['values'] as List?; + final values = valuesValues + ?.map((e) => SentryFeatureFlag.fromJson(Map.from(e))) + .toList(growable: false); + + return SentryFeatureFlags( + values: values ?? [], + unknown: json.notAccessed(), + ); + } + + Map toJson() { + return { + ...?unknown, + 'values': values.map((e) => e.toJson()).toList(growable: false), + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryFeatureFlags copyWith({ + List? values, + Map? unknown, + }) { + return SentryFeatureFlags( + values: values ?? + this.values.map((e) => e.copyWith()).toList(growable: false), + unknown: unknown ?? this.unknown, + ); + } + + @Deprecated('Will be removed in a future version.') + SentryFeatureFlags clone() => copyWith(); +} diff --git a/packages/dart/lib/src/protocol/sentry_feedback.dart b/packages/dart/lib/src/protocol/sentry_feedback.dart new file mode 100644 index 0000000000..a7af41407f --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_feedback.dart @@ -0,0 +1,82 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; +import 'sentry_id.dart'; + +class SentryFeedback { + static const type = 'feedback'; + + SentryFeedback({ + required this.message, + this.contactEmail, + this.name, + this.replayId, + this.url, + this.associatedEventId, + this.unknown, + }); + + String message; + String? contactEmail; + String? name; + String? replayId; + String? url; + SentryId? associatedEventId; + + @internal + final Map? unknown; + + /// Deserializes a [SentryFeedback] from JSON [Map]. + factory SentryFeedback.fromJson(Map data) { + final json = AccessAwareMap(data); + + String? associatedEventId = json['associated_event_id']; + + return SentryFeedback( + message: json['message'], + contactEmail: json['contact_email'], + name: json['name'], + replayId: json['replay_id'], + url: json['url'], + associatedEventId: + associatedEventId != null ? SentryId.fromId(associatedEventId) : null, + unknown: json.notAccessed(), + ); + } + + Map toJson() { + return { + ...?unknown, + 'message': message, + if (contactEmail != null) 'contact_email': contactEmail, + if (name != null) 'name': name, + if (replayId != null) 'replay_id': replayId, + if (url != null) 'url': url, + if (associatedEventId != null) + 'associated_event_id': associatedEventId.toString(), + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryFeedback copyWith({ + String? message, + String? contactEmail, + String? name, + String? replayId, + String? url, + SentryId? associatedEventId, + Map? unknown, + }) => + SentryFeedback( + message: message ?? this.message, + contactEmail: contactEmail ?? this.contactEmail, + name: name ?? this.name, + replayId: replayId ?? this.replayId, + url: url ?? this.url, + associatedEventId: associatedEventId ?? this.associatedEventId, + unknown: unknown ?? this.unknown, + ); + + @Deprecated('Will be removed in a future version.') + SentryFeedback clone() => copyWith(); +} diff --git a/packages/dart/lib/src/protocol/sentry_geo.dart b/packages/dart/lib/src/protocol/sentry_geo.dart new file mode 100644 index 0000000000..e8221c1537 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_geo.dart @@ -0,0 +1,29 @@ +/// Geographical location of the end user or device. +class SentryGeo { + SentryGeo({this.city, this.countryCode, this.region}); + + factory SentryGeo.fromJson(Map json) { + return SentryGeo( + city: json['city'], + countryCode: json['country_code'], + region: json['region'], + ); + } + + /// Human readable city name. + final String? city; + + /// Two-letter country code (ISO 3166-1 alpha-2). + final String? countryCode; + + /// Human readable region name or code. + final String? region; + + Map toJson() { + return { + if (city != null) 'city': city, + if (countryCode != null) 'country_code': countryCode, + if (region != null) 'region': region, + }; + } +} diff --git a/packages/dart/lib/src/protocol/sentry_gpu.dart b/packages/dart/lib/src/protocol/sentry_gpu.dart new file mode 100644 index 0000000000..6ef704978a --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_gpu.dart @@ -0,0 +1,202 @@ +// https://develop.sentry.dev/sdk/event-payloads/contexts/#gpu-context +// Example: +// "gpu": { +// "name": "AMD Radeon Pro 560", +// "vendor_name": "Apple", +// "memory_size": 4096, +// "api_type": "Metal", +// "multi_threaded_rendering": true, +// "version": "Metal", +// "npot_support": "Full" +// } + +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +/// GPU context describes the GPU of the device. +class SentryGpu { + static const type = 'gpu'; + + /// The name of the graphics device. + String? name; + + /// The PCI identifier of the graphics device. + int? id; + + /// The PCI vendor identifier of the graphics device. + String? vendorId; + + /// The vendor name as reported by the graphics device. + String? vendorName; + + /// The total GPU memory available in Megabytes. + int? memorySize; + + /// The device low-level API type. + String? apiType; + + /// Whether the GPU has multi-threaded rendering or not. + bool? multiThreadedRendering; + + /// The Version of the graphics device. + String? version; + + /// The Non-Power-Of-Two-Support support. + String? npotSupport; + + /// Approximate "shader capability" level of the graphics device. + /// For Example: + /// Shader Model 2.0, OpenGL ES 3.0, Metal / OpenGL ES 3.1, 27 (unknown) + String? graphicsShaderLevel; + + /// Largest size of a texture that is supported by the graphics hardware. + /// For Example: 16384 + int? maxTextureSize; + + /// Whether compute shaders are available on the device. + bool? supportsComputeShaders; + + /// Whether GPU draw call instancing is supported. + bool? supportsDrawCallInstancing; + + /// Whether geometry shaders are available on the device. + bool? supportsGeometryShaders; + + /// Whether ray tracing is available on the device. + bool? supportsRayTracing; + + @internal + final Map? unknown; + + SentryGpu({ + this.name, + this.id, + this.vendorId, + this.vendorName, + this.memorySize, + this.apiType, + this.multiThreadedRendering, + this.version, + this.npotSupport, + this.graphicsShaderLevel, + this.maxTextureSize, + this.supportsComputeShaders, + this.supportsDrawCallInstancing, + this.supportsGeometryShaders, + this.supportsRayTracing, + this.unknown, + }); + + /// Deserializes a [SentryGpu] from JSON [Map]. + factory SentryGpu.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryGpu( + name: json['name'], + id: json['id'], + vendorId: json['vendor_id'], + vendorName: json['vendor_name'], + memorySize: json['memory_size'], + apiType: json['api_type'], + multiThreadedRendering: json['multi_threaded_rendering'], + version: json['version'], + npotSupport: json['npot_support'], + graphicsShaderLevel: json['graphics_shader_level'], + maxTextureSize: json['max_texture_size'], + supportsComputeShaders: json['supports_compute_shaders'], + supportsDrawCallInstancing: json['supports_draw_call_instancing'], + supportsGeometryShaders: json['supports_geometry_shaders'], + supportsRayTracing: json['supports_ray_tracing'], + unknown: json.notAccessed(), + ); + } + + @Deprecated('Will be removed in a future version.') + SentryGpu clone() => SentryGpu( + name: name, + id: id, + vendorId: vendorId, + vendorName: vendorName, + memorySize: memorySize, + apiType: apiType, + multiThreadedRendering: multiThreadedRendering, + version: version, + npotSupport: npotSupport, + graphicsShaderLevel: graphicsShaderLevel, + maxTextureSize: maxTextureSize, + supportsComputeShaders: supportsComputeShaders, + supportsDrawCallInstancing: supportsDrawCallInstancing, + supportsGeometryShaders: supportsGeometryShaders, + supportsRayTracing: supportsRayTracing, + unknown: unknown, + ); + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + if (name != null) 'name': name, + if (id != null) 'id': id, + if (vendorId != null) 'vendor_id': vendorId, + if (vendorName != null) 'vendor_name': vendorName, + if (memorySize != null) 'memory_size': memorySize, + if (apiType != null) 'api_type': apiType, + if (multiThreadedRendering != null) + 'multi_threaded_rendering': multiThreadedRendering, + if (version != null) 'version': version, + if (npotSupport != null) 'npot_support': npotSupport, + if (graphicsShaderLevel != null) + 'graphics_shader_level': graphicsShaderLevel, + if (maxTextureSize != null) 'max_texture_size': maxTextureSize, + if (supportsComputeShaders != null) + 'supports_compute_shaders': supportsComputeShaders, + if (supportsDrawCallInstancing != null) + 'supports_draw_call_instancing': supportsDrawCallInstancing, + if (supportsGeometryShaders != null) + 'supports_geometry_shaders': supportsGeometryShaders, + if (supportsRayTracing != null) + 'supports_ray_tracing': supportsRayTracing, + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryGpu copyWith({ + String? name, + int? id, + String? vendorId, + String? vendorName, + int? memorySize, + String? apiType, + bool? multiThreadedRendering, + String? version, + String? npotSupport, + String? graphicsShaderLevel, + int? maxTextureSize, + bool? supportsComputeShaders, + bool? supportsDrawCallInstancing, + bool? supportsGeometryShaders, + bool? supportsRayTracing, + }) => + SentryGpu( + name: name ?? this.name, + id: id ?? this.id, + vendorId: vendorId ?? this.vendorId, + vendorName: vendorName ?? this.vendorName, + memorySize: memorySize ?? this.memorySize, + apiType: apiType ?? this.apiType, + multiThreadedRendering: + multiThreadedRendering ?? this.multiThreadedRendering, + version: version ?? this.version, + npotSupport: npotSupport ?? this.npotSupport, + graphicsShaderLevel: graphicsShaderLevel ?? this.graphicsShaderLevel, + maxTextureSize: maxTextureSize ?? this.maxTextureSize, + supportsComputeShaders: + supportsComputeShaders ?? this.supportsComputeShaders, + supportsDrawCallInstancing: + supportsDrawCallInstancing ?? this.supportsDrawCallInstancing, + supportsGeometryShaders: + supportsGeometryShaders ?? this.supportsGeometryShaders, + supportsRayTracing: supportsRayTracing ?? this.supportsRayTracing, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_id.dart b/packages/dart/lib/src/protocol/sentry_id.dart new file mode 100644 index 0000000000..500c8980a2 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_id.dart @@ -0,0 +1,40 @@ +import 'package:meta/meta.dart'; +import 'package:uuid/uuid.dart'; + +/// Hexadecimal string representing a uuid4 value. +/// The length is exactly 32 +/// characters. Dashes are not allowed. Has to be lowercase. +@immutable +class SentryId { + /// The ID Sentry.io assigned to the submitted event for future reference. + final String _id; + + static final Uuid _uuidGenerator = Uuid(); + + SentryId._internal({String? id}) + : _id = + id?.replaceAll('-', '') ?? _uuidGenerator.v4().replaceAll('-', ''); + + /// Generates a new SentryId + SentryId.newId() : this._internal(); + + /// Generates a SentryId with the given UUID + SentryId.fromId(String id) : this._internal(id: id); + + /// SentryId with an empty UUID + const SentryId.empty() : _id = '00000000000000000000000000000000'; + + @override + String toString() => _id; + + @override + int get hashCode => _id.hashCode; + + @override + bool operator ==(o) { + if (o is SentryId) { + return o._id == _id; + } + return false; + } +} diff --git a/packages/dart/lib/src/protocol/sentry_level.dart b/packages/dart/lib/src/protocol/sentry_level.dart new file mode 100644 index 0000000000..264a7c43ab --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_level.dart @@ -0,0 +1,57 @@ +import 'package:meta/meta.dart'; + +/// Severity of the logged [Event]. +@immutable +class SentryLevel { + const SentryLevel._(this.name, this.ordinal); + + static const fatal = SentryLevel._('fatal', 5); + static const error = SentryLevel._('error', 4); + static const warning = SentryLevel._('warning', 3); + static const info = SentryLevel._('info', 2); + static const debug = SentryLevel._('debug', 1); + + /// API name of the level as it is encoded in the JSON protocol. + final String name; + final int ordinal; + + factory SentryLevel.fromName(String name) { + switch (name) { + case 'fatal': + return SentryLevel.fatal; + case 'error': + return SentryLevel.error; + case 'warning': + return SentryLevel.warning; + case 'info': + return SentryLevel.info; + } + return SentryLevel.debug; + } + + /// For use with Dart's + /// [`log`](https://api.dart.dev/stable/2.12.4/dart-developer/log.html) + /// function. + /// These levels are inspired by + /// https://pub.dev/documentation/logging/latest/logging/Level-class.html + int toDartLogLevel() { + switch (this) { + // Level.SHOUT + case SentryLevel.fatal: + return 1200; + // Level.SEVERE + case SentryLevel.error: + return 1000; + // Level.SEVERE + case SentryLevel.warning: + return 900; + // Level.INFO + case SentryLevel.info: + return 800; + // Level.CONFIG + case SentryLevel.debug: + return 700; + } + return 700; + } +} diff --git a/packages/dart/lib/src/protocol/sentry_log.dart b/packages/dart/lib/src/protocol/sentry_log.dart new file mode 100644 index 0000000000..55c0174d90 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_log.dart @@ -0,0 +1,35 @@ +import 'sentry_id.dart'; +import 'sentry_log_level.dart'; +import 'sentry_log_attribute.dart'; + +class SentryLog { + DateTime timestamp; + SentryId traceId; + SentryLogLevel level; + String body; + Map attributes; + int? severityNumber; + + /// The traceId is initially an empty default value and is populated during event processing; + /// by the time processing completes, it is guaranteed to be a valid non-empty trace id. + SentryLog({ + required this.timestamp, + SentryId? traceId, + required this.level, + required this.body, + required this.attributes, + this.severityNumber, + }) : traceId = traceId ?? SentryId.empty(); + + Map toJson() { + return { + 'timestamp': timestamp.toIso8601String(), + 'trace_id': traceId.toString(), + 'level': level.value, + 'body': body, + 'attributes': + attributes.map((key, value) => MapEntry(key, value.toJson())), + 'severity_number': severityNumber ?? level.toSeverityNumber(), + }; + } +} diff --git a/packages/dart/lib/src/protocol/sentry_log_attribute.dart b/packages/dart/lib/src/protocol/sentry_log_attribute.dart new file mode 100644 index 0000000000..63ac85eb87 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_log_attribute.dart @@ -0,0 +1,30 @@ +class SentryLogAttribute { + final dynamic value; + final String type; + + const SentryLogAttribute._(this.value, this.type); + + factory SentryLogAttribute.string(String value) { + return SentryLogAttribute._(value, 'string'); + } + + factory SentryLogAttribute.bool(bool value) { + return SentryLogAttribute._(value, 'boolean'); + } + + factory SentryLogAttribute.int(int value) { + return SentryLogAttribute._(value, 'integer'); + } + + factory SentryLogAttribute.double(double value) { + return SentryLogAttribute._(value, 'double'); + } + + // In the future the SDK will also support List, List, List, List values. + Map toJson() { + return { + 'value': value, + 'type': type, + }; + } +} diff --git a/packages/dart/lib/src/protocol/sentry_log_level.dart b/packages/dart/lib/src/protocol/sentry_log_level.dart new file mode 100644 index 0000000000..aac0b386bc --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_log_level.dart @@ -0,0 +1,28 @@ +enum SentryLogLevel { + trace('trace'), + debug('debug'), + info('info'), + warn('warn'), + error('error'), + fatal('fatal'); + + final String value; + const SentryLogLevel(this.value); + + int toSeverityNumber() { + switch (this) { + case SentryLogLevel.trace: + return 1; + case SentryLogLevel.debug: + return 5; + case SentryLogLevel.info: + return 9; + case SentryLogLevel.warn: + return 13; + case SentryLogLevel.error: + return 17; + case SentryLogLevel.fatal: + return 21; + } + } +} diff --git a/packages/dart/lib/src/protocol/sentry_message.dart b/packages/dart/lib/src/protocol/sentry_message.dart new file mode 100644 index 0000000000..9cddad8851 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_message.dart @@ -0,0 +1,70 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +/// The Message Interface carries a log message that describes an event or error. +/// Optionally, it can carry a format string and structured parameters. This can help to group similar messages into the same issue. +/// example of a serialized message: +/// ```json +/// { +/// "message": { +/// "message": "My raw message with interpreted strings like %s", +/// "params": ["this"] +/// } +/// } +/// ``` +class SentryMessage { + /// The fully formatted message. If missing, Sentry will try to interpolate the message. + String formatted; + + /// The raw message string (uninterpolated). + /// example : "My raw message with interpreted strings like %s", + String? template; + + /// A list of formatting parameters, preferably strings. Non-strings will be coerced to strings. + List? params; + + @internal + final Map? unknown; + + SentryMessage( + this.formatted, { + this.template, + this.params, + this.unknown, + }); + + /// Deserializes a [SentryMessage] from JSON [Map]. + factory SentryMessage.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryMessage( + json['formatted'], + template: json['message'], + params: json['params'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + 'formatted': formatted, + if (template != null) 'message': template, + if (params?.isNotEmpty ?? false) 'params': params, + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryMessage copyWith({ + String? formatted, + String? template, + List? params, + }) => + SentryMessage( + formatted ?? this.formatted, + template: template ?? this.template, + params: params ?? this.params, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_operating_system.dart b/packages/dart/lib/src/protocol/sentry_operating_system.dart new file mode 100644 index 0000000000..50f5e4f8ae --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_operating_system.dart @@ -0,0 +1,130 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +/// Describes the operating system on which the event was created. +/// +/// In web contexts, this is the operating system of the browse +/// (normally pulled from the User-Agent string). +class SentryOperatingSystem { + static const type = 'os'; + + SentryOperatingSystem({ + this.name, + this.version, + this.build, + this.kernelVersion, + this.rooted, + this.rawDescription, + this.theme, + this.unknown, + }); + + /// The name of the operating system. + String? name; + + /// The version of the operating system. + String? version; + + /// The internal build revision of the operating system. + String? build; + + /// An independent kernel version string. + /// + /// This is typically the entire output of the `uname` syscall. + String? kernelVersion; + + /// A flag indicating whether the OS has been jailbroken or rooted. + bool? rooted; + + /// An unprocessed description string obtained by the operating system. + /// + /// For some well-known runtimes, Sentry will attempt to parse name and + /// version from this string, if they are not explicitly given. + String? rawDescription; + + /// Optional. Either light or dark. + /// Describes whether the OS runs in dark mode or not. + String? theme; + + @internal + final Map? unknown; + + /// Deserializes a [SentryOperatingSystem] from JSON [Map]. + factory SentryOperatingSystem.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryOperatingSystem( + name: json['name'], + version: json['version'], + build: json['build'], + kernelVersion: json['kernel_version'], + rooted: json['rooted'], + rawDescription: json['raw_description'], + theme: json['theme'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + if (name != null) 'name': name, + if (version != null) 'version': version, + if (build != null) 'build': build, + if (kernelVersion != null) 'kernel_version': kernelVersion, + if (rooted != null) 'rooted': rooted, + if (rawDescription != null) 'raw_description': rawDescription, + if (theme != null) 'theme': theme, + }; + } + + @Deprecated('Will be removed in a future version.') + SentryOperatingSystem clone() => SentryOperatingSystem( + name: name, + version: version, + build: build, + kernelVersion: kernelVersion, + rooted: rooted, + rawDescription: rawDescription, + theme: theme, + unknown: unknown, + ); + + @Deprecated('Assign values directly to the instance.') + SentryOperatingSystem copyWith({ + String? name, + String? version, + String? build, + String? kernelVersion, + bool? rooted, + String? rawDescription, + String? theme, + }) => + SentryOperatingSystem( + name: name ?? this.name, + version: version ?? this.version, + build: build ?? this.build, + kernelVersion: kernelVersion ?? this.kernelVersion, + rooted: rooted ?? this.rooted, + rawDescription: rawDescription ?? this.rawDescription, + theme: theme ?? this.theme, + unknown: unknown, + ); + + SentryOperatingSystem mergeWith(SentryOperatingSystem other) => + SentryOperatingSystem( + name: other.name ?? name, + version: other.version ?? version, + build: other.build ?? build, + kernelVersion: other.kernelVersion ?? kernelVersion, + rooted: other.rooted ?? rooted, + rawDescription: other.rawDescription ?? rawDescription, + theme: other.theme ?? theme, + unknown: other.unknown == null + ? unknown + : unknown == null + ? null + : {...unknown!, ...other.unknown!}, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_package.dart b/packages/dart/lib/src/protocol/sentry_package.dart new file mode 100644 index 0000000000..f0e2491232 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_package.dart @@ -0,0 +1,48 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +/// A [SentryPackage] part of the SDK. +class SentryPackage { + /// Creates an [SentryPackage] object that is part of the SDK. + SentryPackage(this.name, this.version, {this.unknown}); + + /// The name of the SDK. + String name; + + /// The version of the SDK. + String version; + + @internal + final Map? unknown; + + /// Deserializes a [SentryPackage] from JSON [Map]. + factory SentryPackage.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryPackage( + json['name'], + json['version'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + 'name': name, + 'version': version, + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryPackage copyWith({ + String? name, + String? version, + }) => + SentryPackage( + name ?? this.name, + version ?? this.version, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_proxy.dart b/packages/dart/lib/src/protocol/sentry_proxy.dart new file mode 100644 index 0000000000..52237e9b1f --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_proxy.dart @@ -0,0 +1,63 @@ +class SentryProxy { + final SentryProxyType type; + final String? host; + final int? port; + final String? user; + final String? pass; + + SentryProxy({required this.type, this.host, this.port, this.user, this.pass}); + + String toPacString() { + String type = 'DIRECT'; + switch (this.type) { + case SentryProxyType.direct: + return 'DIRECT'; + case SentryProxyType.http: + type = 'PROXY'; + break; + case SentryProxyType.socks: + type = 'SOCKS'; + break; + } + if (host != null && port != null) { + return '$type $host:$port'; + } else if (host != null) { + return '$type $host'; + } else { + return 'DIRECT'; + } + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + if (host != null) 'host': host, + if (port != null) 'port': port, + 'type': type.toString().split('.').last.toUpperCase(), + if (user != null) 'user': user, + if (pass != null) 'pass': pass, + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryProxy copyWith({ + String? host, + int? port, + SentryProxyType? type, + String? user, + String? pass, + }) => + SentryProxy( + host: host ?? this.host, + port: port ?? this.port, + type: type ?? this.type, + user: user ?? this.user, + pass: pass ?? this.pass, + ); +} + +enum SentryProxyType { + direct, + http, + socks; +} diff --git a/packages/dart/lib/src/protocol/sentry_request.dart b/packages/dart/lib/src/protocol/sentry_request.dart new file mode 100644 index 0000000000..6bdae7a352 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_request.dart @@ -0,0 +1,178 @@ +import 'package:meta/meta.dart'; + +import '../utils/http_sanitizer.dart'; +import '../utils/iterable_utils.dart'; +import 'access_aware_map.dart'; + +/// The Request interface contains information on a HTTP request related to the event. +/// In client SDKs, this can be an outgoing request, or the request that rendered the current web page. +/// On server SDKs, this could be the incoming web request that is being handled. +class SentryRequest { + ///The URL of the request if available. + ///The query string can be declared either as part of the url, + ///or separately in queryString. + String? url; + + ///The HTTP method of the request. + String? method; + + /// The query string component of the URL. + /// + /// If the query string is not declared and part of the url parameter, + /// Sentry moves it to the query string. + String? queryString; + + /// The cookie values as string. + String? cookies; + + dynamic _data; + + /// Submitted data in a format that makes the most sense. + /// SDKs should discard large bodies by default. + /// Can be given as string or structural data of any format. + dynamic get data { + if (_data is List) { + return List.unmodifiable(_data); + } else if (_data is Map) { + return Map.unmodifiable(_data); + } + + return _data; + } + + Map? _headers; + + /// An immutable dictionary of submitted headers. + /// If a header appears multiple times it, + /// needs to be merged according to the HTTP standard for header merging. + /// Header names are treated case-insensitively by Sentry. + Map get headers => Map.unmodifiable(_headers ?? const {}); + + set headers(Map headers) { + _headers = Map.of(headers); + } + + Map? _env; + + /// An immutable dictionary containing environment information passed from the server. + /// This is where information such as CGI/WSGI/Rack keys go that are not HTTP headers. + Map get env => Map.unmodifiable(_env ?? const {}); + + /// The fragment of the request URL. + String? fragment; + + /// The API target/specification that made the request. + /// Values can be `graphql`, `rest`, etc. + /// + /// The data field should contain the request and response bodies based on + /// its target specification. + String? apiTarget; + + @internal + final Map? unknown; + + SentryRequest({ + this.url, + this.method, + this.queryString, + String? cookies, + this.fragment, + this.apiTarget, + dynamic data, + Map? headers, + Map? env, + this.unknown, + }) : _data = data, + _headers = headers != null ? Map.from(headers) : null, + // Look for a 'Set-Cookie' header (case insensitive) if not given. + cookies = cookies ?? + IterableUtils.firstWhereOrNull( + headers?.entries, + (MapEntry e) => e.key.toLowerCase() == 'cookie', + )?.value, + _env = env != null ? Map.from(env) : null; + + factory SentryRequest.fromUri({ + required Uri uri, + String? method, + String? cookies, + dynamic data, + Map? headers, + Map? env, + String? apiTarget, + }) { + final request = SentryRequest( + url: uri.toString(), + method: method, + cookies: cookies, + data: data, + headers: headers, + env: env, + queryString: uri.query, + fragment: uri.fragment, + // ignore: deprecated_member_use_from_same_package + apiTarget: apiTarget, + ); + request.sanitize(); + return request; + } + + /// Deserializes a [SentryRequest] from JSON [Map]. + factory SentryRequest.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryRequest( + url: json['url'], + method: json['method'], + queryString: json['query_string'], + cookies: json['cookies'], + data: json['data'], + headers: json.containsKey('headers') ? Map.from(json['headers']) : null, + env: json.containsKey('env') ? Map.from(json['env']) : null, + fragment: json['fragment'], + apiTarget: json['api_target'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + if (url != null) 'url': url, + if (method != null) 'method': method, + if (queryString != null) 'query_string': queryString, + if (_data != null) 'data': _data, + if (cookies != null) 'cookies': cookies, + if (headers.isNotEmpty) 'headers': headers, + if (env.isNotEmpty) 'env': env, + if (fragment != null) 'fragment': fragment, + if (apiTarget != null) 'api_target': apiTarget, + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryRequest copyWith({ + String? url, + String? method, + String? queryString, + String? cookies, + String? fragment, + dynamic data, + Map? headers, + Map? env, + bool removeCookies = false, + String? apiTarget, + }) => + SentryRequest( + url: url ?? this.url, + method: method ?? this.method, + queryString: queryString ?? this.queryString, + cookies: removeCookies ? null : cookies ?? this.cookies, + data: data ?? _data, + headers: headers ?? _headers, + env: env ?? _env, + fragment: fragment ?? this.fragment, + apiTarget: apiTarget ?? this.apiTarget, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_response.dart b/packages/dart/lib/src/protocol/sentry_response.dart new file mode 100644 index 0000000000..c7aa527835 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_response.dart @@ -0,0 +1,106 @@ +import 'contexts.dart'; +import '../utils/iterable_utils.dart'; + +/// The response interface contains information on a HTTP request related to the event. +class SentryResponse { + /// The type of this class in the [Contexts] field + static const String type = 'response'; + + /// The size of the response body. + int? bodySize; + + /// The HTTP status code of the response. + /// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status + int? statusCode; + + /// An immutable dictionary of submitted headers. + /// If a header appears multiple times it, + /// needs to be merged according to the HTTP standard for header merging. + /// Header names are treated case-insensitively by Sentry. + Map get headers => Map.unmodifiable(_headers ?? const {}); + + Map? _headers; + + /// Cookie key-value pairs as string. + String? cookies; + + Object? _data; + + /// Response data in any format that makes sense. + /// + /// SDKs should discard large and binary bodies by default. + /// Can be given as a string or structural data of any format. + Object? get data { + final typedData = _data; + if (typedData is List) { + return List.unmodifiable(typedData); + } else if (typedData is Map) { + return Map.unmodifiable(typedData); + } + + return _data; + } + + SentryResponse({ + this.bodySize, + this.statusCode, + Map? headers, + String? cookies, + Object? data, + }) : _data = data, + _headers = headers != null ? Map.from(headers) : null, + // Look for a 'Set-Cookie' header (case insensitive) if not given. + cookies = cookies ?? + IterableUtils.firstWhereOrNull( + headers?.entries, + (MapEntry e) => + e.key.toLowerCase() == 'set-cookie', + )?.value; + + /// Deserializes a [SentryResponse] from JSON [Map]. + factory SentryResponse.fromJson(Map json) { + return SentryResponse( + headers: json.containsKey('headers') ? Map.from(json['headers']) : null, + cookies: json['cookies'], + bodySize: json['body_size'], + statusCode: json['status_code'], + data: json['data'], + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + if (headers.isNotEmpty) 'headers': headers, + if (cookies != null) 'cookies': cookies, + if (bodySize != null) 'body_size': bodySize, + if (statusCode != null) 'status_code': statusCode, + if (data != null) 'data': data, + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryResponse copyWith({ + int? statusCode, + int? bodySize, + Map? headers, + String? cookies, + Object? data, + }) => + SentryResponse( + headers: headers ?? _headers, + cookies: cookies ?? this.cookies, + bodySize: bodySize ?? this.bodySize, + statusCode: statusCode ?? this.statusCode, + data: data ?? this.data, + ); + + @Deprecated('Will be removed in a future version.') + SentryResponse clone() => SentryResponse( + bodySize: bodySize, + headers: headers, + cookies: cookies, + statusCode: statusCode, + data: data, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_runtime.dart b/packages/dart/lib/src/protocol/sentry_runtime.dart new file mode 100644 index 0000000000..9fd8e552ab --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_runtime.dart @@ -0,0 +1,105 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +/// Describes a runtime in more detail. +/// +/// Typically this context is used multiple times if multiple runtimes +/// are involved (for instance if you have a JavaScript application running +/// on top of JVM). +class SentryRuntime { + static const listType = 'runtimes'; + static const type = 'runtime'; + + SentryRuntime({ + this.key, + this.name, + this.version, + this.compiler, + this.rawDescription, + this.build, + this.unknown, + }) : assert(key == null || key.isNotEmpty); + + /// Key used in the JSON and which will be displayed + /// in the Sentry UI. Defaults to lower case version of [name]. + /// + /// Unused if only one [SentryRuntime] is provided in [Contexts]. + String? key; + + /// The name of the runtime. + String? name; + + /// The version identifier of the runtime. + String? version; + + /// Dart has a couple different compilers. + /// E.g: dart2js, dartdevc, AOT, VM + String? compiler; + + /// An unprocessed description string obtained by the runtime. + /// + /// For some well-known runtimes, Sentry will attempt to parse name + /// and version from this string, if they are not explicitly given. + String? rawDescription; + + /// Application build string, if it is separate from the version. + String? build; + + @internal + final Map? unknown; + + /// Deserializes a [SentryRuntime] from JSON [Map]. + factory SentryRuntime.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryRuntime( + name: json['name'], + version: json['version'], + compiler: json['compiler'], + rawDescription: json['raw_description'], + build: json['build'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + if (name != null) 'name': name, + if (compiler != null) 'compiler': compiler, + if (version != null) 'version': version, + if (rawDescription != null) 'raw_description': rawDescription, + if (build != null) 'build': build, + }; + } + + @Deprecated('Will be removed in a future version.') + SentryRuntime clone() => SentryRuntime( + key: key, + name: name, + version: version, + compiler: compiler, + rawDescription: rawDescription, + build: build, + unknown: unknown, + ); + + SentryRuntime copyWith({ + String? key, + String? name, + String? version, + String? compiler, + String? rawDescription, + String? build, + }) => + SentryRuntime( + key: key ?? this.key, + name: name ?? this.name, + version: version ?? this.version, + compiler: compiler ?? this.compiler, + rawDescription: rawDescription ?? this.rawDescription, + build: build ?? this.build, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_span.dart b/packages/dart/lib/src/protocol/sentry_span.dart new file mode 100644 index 0000000000..e93a5f96fd --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_span.dart @@ -0,0 +1,244 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; + +import '../../sentry.dart'; +import '../sentry_tracer.dart'; + +typedef OnFinishedCallback = Future Function( + {DateTime? endTimestamp, Hint? hint}); + +class SentrySpan extends ISentrySpan { + final SentrySpanContext _context; + DateTime? _endTimestamp; + late final DateTime _startTimestamp; + final Hub _hub; + + bool _isRootSpan = false; + + bool get isRootSpan => _isRootSpan; + + @internal + SentryTracer get tracer => _tracer; + + final SentryTracer _tracer; + + final Map _data = {}; + dynamic _throwable; + + SpanStatus? _status; + final Map _tags = {}; + OnFinishedCallback? _finishedCallback; + + @override + final SentryTracesSamplingDecision? samplingDecision; + + SentrySpan( + this._tracer, + this._context, + this._hub, { + DateTime? startTimestamp, + this.samplingDecision, + OnFinishedCallback? finishedCallback, + isRootSpan = false, + }) { + _startTimestamp = startTimestamp?.toUtc() ?? _hub.options.clock(); + _finishedCallback = finishedCallback; + _origin = _context.origin; + _isRootSpan = isRootSpan; + } + + @override + Future finish( + {SpanStatus? status, DateTime? endTimestamp, Hint? hint}) async { + if (finished) { + return; + } + + if (status != null) { + _status = status; + } + + if (endTimestamp == null) { + endTimestamp = _hub.options.clock(); + } else if (endTimestamp.isBefore(_startTimestamp)) { + _hub.options.log( + SentryLevel.warning, + 'End timestamp ($endTimestamp) cannot be before start timestamp ($_startTimestamp)', + ); + endTimestamp = _hub.options.clock(); + } else { + endTimestamp = endTimestamp.toUtc(); + } + + for (final collector in _hub.options.performanceCollectors) { + if (collector is PerformanceContinuousCollector) { + await collector.onSpanFinished(this, endTimestamp); + } + } + + // The finished flag depends on the _endTimestamp + // If we set this earlier then finished is true and then we cannot use setData etc... + _endTimestamp = endTimestamp; + + // associate error + if (_throwable != null) { + _hub.setSpanContext(_throwable, this, _tracer.name); + } + await _finishedCallback?.call(endTimestamp: _endTimestamp, hint: hint); + return super + .finish(status: status, endTimestamp: _endTimestamp, hint: hint); + } + + @override + void removeData(String key) { + if (finished) { + return; + } + + _data.remove(key); + } + + @override + void removeTag(String key) { + if (finished) { + return; + } + + _tags.remove(key); + } + + @override + void setData(String key, value) { + if (finished) { + return; + } + + _data[key] = value; + } + + @override + void setTag(String key, String value) { + if (finished) { + return; + } + + _tags[key] = value; + } + + @override + ISentrySpan startChild( + String operation, { + String? description, + DateTime? startTimestamp, + }) { + if (finished) { + return NoOpSentrySpan(); + } + + if (startTimestamp?.isBefore(_startTimestamp) ?? false) { + _hub.options.log( + SentryLevel.warning, + "Start timestamp ($startTimestamp) cannot be before parent span's start timestamp ($_startTimestamp). Returning NoOpSpan.", + ); + return NoOpSentrySpan(); + } + + return _tracer.startChildWithParentSpanId( + _context.spanId, + operation, + description: description, + startTimestamp: startTimestamp, + ); + } + + @override + SpanStatus? get status => _status; + + @override + set status(SpanStatus? status) => _status = status; + + @override + DateTime get startTimestamp => _startTimestamp; + + @override + DateTime? get endTimestamp => _endTimestamp; + + @override + SentrySpanContext get context => _context; + + String? _origin; + + @override + String? get origin => _origin; + + @override + set origin(String? origin) => _origin = origin; + + Map toJson() { + final json = _context.toJson(); + json['start_timestamp'] = + formatDateAsIso8601WithMillisPrecision(_startTimestamp); + if (_endTimestamp != null) { + json['timestamp'] = + formatDateAsIso8601WithMillisPrecision(_endTimestamp!); + } + if (_data.isNotEmpty) { + json['data'] = _data; + } + if (status != null) { + json['status'] = status.toString(); + } + if (_tags.isNotEmpty) { + json['tags'] = _tags; + } + if (_origin != null) { + json['origin'] = _origin; + } + + return json; + } + + @override + bool get finished => _endTimestamp != null; + + @override + dynamic get throwable => _throwable; + + @override + set throwable(throwable) => _throwable = throwable; + + Map get tags => _tags; + + Map get data => _data; + + @override + SentryTraceHeader toSentryTrace() => generateSentryTraceHeader( + traceId: _context.traceId, + spanId: _context.spanId, + sampled: samplingDecision?.sampled, + ); + + @override + void setMeasurement( + String name, + num value, { + SentryMeasurementUnit? unit, + }) { + if (finished) { + _hub.options.log(SentryLevel.debug, + "The span is already finished. Measurement $name cannot be set"); + return; + } + _tracer.setMeasurementFromChild(name, value, unit: unit); + } + + @override + SentryBaggageHeader? toBaggageHeader() => _tracer.toBaggageHeader(); + + @override + SentryTraceContextHeader? traceContext() => _tracer.traceContext(); + + @override + void scheduleFinish() => _tracer.scheduleFinish(); +} diff --git a/packages/dart/lib/src/protocol/sentry_stack_frame.dart b/packages/dart/lib/src/protocol/sentry_stack_frame.dart new file mode 100644 index 0000000000..244f39a037 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_stack_frame.dart @@ -0,0 +1,237 @@ +import 'package:meta/meta.dart'; +import 'access_aware_map.dart'; + +/// Frames belong to a StackTrace +/// It should contain at least a filename, function or instruction_addr +class SentryStackFrame { + SentryStackFrame({ + this.absPath, + this.fileName, + this.function, + this.module, + this.lineNo, + this.colNo, + this.contextLine, + this.inApp, + this.package, + this.native, + this.platform, + this.imageAddr, + this.symbolAddr, + this.instructionAddr, + this.rawFunction, + this.stackStart, + this.symbol, + List? framesOmitted, + List? preContext, + List? postContext, + Map? vars, + this.unknown, + }) : _framesOmitted = + framesOmitted != null ? List.from(framesOmitted) : null, + _preContext = preContext != null ? List.from(preContext) : null, + _postContext = postContext != null ? List.from(postContext) : null, + _vars = vars != null ? Map.from(vars) : null; + + /// The absolute path to filename. + String? absPath; + + List? _preContext; + + /// An immutable list of source code lines before context_line (in order) – usually `lineno - 5:lineno`. + List get preContext => List.unmodifiable(_preContext ?? const []); + + List? _postContext; + + /// An immutable list of source code lines after context_line (in order) – usually `lineno + 1:lineno + 5`. + List get postContext => List.unmodifiable(_postContext ?? const []); + + Map? _vars; + + /// An immutable mapping of variables which were available within this frame (usually context-locals). + Map get vars => Map.unmodifiable(_vars ?? const {}); + + List? _framesOmitted; + + /// Which frames were omitted, if any. + /// + /// If the list of frames is large, you can explicitly tell the system + /// that you’ve omitted a range of frames. + /// The frames_omitted must be a single tuple two values: start and end. + // + /// Example : If you only removed the 8th frame, the value would be (8, 9), + /// meaning it started at the 8th frame, and went until the 9th (the number of frames omitted is end-start). + /// The values should be based on a one-index. + List get framesOmitted => List.unmodifiable(_framesOmitted ?? const []); + + /// The relative file path to the call. + String? fileName; + + /// The name of the function being called. + String? function; + + /// Platform-specific module path. + String? module; + + /// The column number of the call + int? lineNo; + + /// The column number of the call + int? colNo; + + /// Source code in filename at line number. + String? contextLine; + + /// Signifies whether this frame is related to the execution of the relevant code in this stacktrace. + /// + /// For example, the frames that might power the framework’s web server of your app are probably not relevant, however calls to the framework’s library once you start handling code likely are. + bool? inApp; + + /// The "package" the frame was contained in. + String? package; + + // TODO what is this? doesn't seem to be part of the spec https://develop.sentry.dev/sdk/event-payloads/stacktrace/ + bool? native; + + /// This can override the platform for a single frame. Otherwise, the platform of the event is assumed. This can be used for multi-platform stack traces + String? platform; + + /// Optionally an address of the debug image to reference. + String? imageAddr; + + /// An optional address that points to a symbol. We use the instruction address for symbolication, but this can be used to calculate an instruction offset automatically. + String? symbolAddr; + + /// The instruction address + /// The official docs refer to it as 'The difference between instruction address and symbol address in bytes.' + String? instructionAddr; + + /// The original function name, if the function name is shortened or demangled. Sentry shows the raw function when clicking on the shortened one in the UI. + String? rawFunction; + + /// Marks this frame as the bottom of a chained stack trace. + /// + /// Stack traces from asynchronous code consist of several sub traces that + /// are chained together into one large list. This flag indicates the root + /// function of a chained stack trace. Depending on the runtime and thread, + /// this is either the main function or a thread base stub. + /// + /// This field should only be specified when true. + bool? stackStart; + + /// Potentially mangled name of the symbol as it appears in an executable. + /// + /// This is different from a function name by generally being the mangled name + /// that appears natively in the binary. + /// This is relevant for languages like Swift, C++ or Rust. + String? symbol; + + @internal + final Map? unknown; + + /// Deserializes a [SentryStackFrame] from JSON [Map]. + factory SentryStackFrame.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryStackFrame( + absPath: json['abs_path'], + fileName: json['filename'], + function: json['function'], + module: json['module'], + lineNo: json['lineno'], + colNo: json['colno'], + contextLine: json['context_line'], + inApp: json['in_app'], + package: json['package'], + native: json['native'], + platform: json['platform'], + imageAddr: json['image_addr'], + symbolAddr: json['symbol_addr'], + instructionAddr: json['instruction_addr'], + rawFunction: json['raw_function'], + framesOmitted: json['frames_omitted'], + preContext: json['pre_context'], + postContext: json['post_context'], + vars: json['vars'], + symbol: json['symbol'], + stackStart: json['stack_start'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + if (_preContext?.isNotEmpty ?? false) 'pre_context': _preContext, + if (_postContext?.isNotEmpty ?? false) 'post_context': _postContext, + if (_vars?.isNotEmpty ?? false) 'vars': _vars, + if (_framesOmitted?.isNotEmpty ?? false) 'frames_omitted': _framesOmitted, + if (fileName != null) 'filename': fileName, + if (package != null) 'package': package, + if (function != null) 'function': function, + if (module != null) 'module': module, + if (lineNo != null) 'lineno': lineNo, + if (colNo != null) 'colno': colNo, + if (absPath != null) 'abs_path': absPath, + if (contextLine != null) 'context_line': contextLine, + if (inApp != null) 'in_app': inApp, + if (native != null) 'native': native, + if (platform != null) 'platform': platform, + if (imageAddr != null) 'image_addr': imageAddr, + if (symbolAddr != null) 'symbol_addr': symbolAddr, + if (instructionAddr != null) 'instruction_addr': instructionAddr, + if (rawFunction != null) 'raw_function': rawFunction, + if (symbol != null) 'symbol': symbol, + if (stackStart != null) 'stack_start': stackStart, + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryStackFrame copyWith({ + String? absPath, + String? fileName, + String? function, + String? module, + int? lineNo, + int? colNo, + String? contextLine, + bool? inApp, + String? package, + bool? native, + String? platform, + String? imageAddr, + String? symbolAddr, + String? instructionAddr, + String? rawFunction, + List? framesOmitted, + List? preContext, + List? postContext, + Map? vars, + bool? stackStart, + String? symbol, + }) => + SentryStackFrame( + absPath: absPath ?? this.absPath, + fileName: fileName ?? this.fileName, + function: function ?? this.function, + module: module ?? this.module, + lineNo: lineNo ?? this.lineNo, + colNo: colNo ?? this.colNo, + contextLine: contextLine ?? this.contextLine, + inApp: inApp ?? this.inApp, + package: package ?? this.package, + native: native ?? this.native, + platform: platform ?? this.platform, + imageAddr: imageAddr ?? this.imageAddr, + symbolAddr: symbolAddr ?? this.symbolAddr, + instructionAddr: instructionAddr ?? this.instructionAddr, + rawFunction: rawFunction ?? this.rawFunction, + framesOmitted: framesOmitted ?? _framesOmitted, + preContext: preContext ?? _preContext, + postContext: postContext ?? _postContext, + vars: vars ?? _vars, + symbol: symbol ?? symbol, + stackStart: stackStart ?? stackStart, + unknown: unknown, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_stack_trace.dart b/packages/dart/lib/src/protocol/sentry_stack_trace.dart new file mode 100644 index 0000000000..c6908d9863 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_stack_trace.dart @@ -0,0 +1,105 @@ +import 'package:meta/meta.dart'; + +import 'sentry_stack_frame.dart'; +import 'access_aware_map.dart'; + +/// Stacktrace holds information about the frames of the stack. +class SentryStackTrace { + SentryStackTrace({ + required List frames, + Map? registers, + this.lang, + this.snapshot, + this.unknown, + @internal this.baseAddr, + @internal this.buildId, + }) : _frames = frames, + _registers = Map.from(registers ?? {}); + + List? _frames; + + /// Required. A non-empty immutable list of stack frames (see below). + /// The list is ordered from caller to callee, or oldest to youngest. + /// The last frame is the one creating the exception. + List get frames => List.unmodifiable(_frames ?? const []); + + Map? _registers; + + /// Optional. A map of register names and their values. + /// The values should contain the actual register values of the thread, + /// thus mapping to the last frame in the list. + Map get registers => Map.unmodifiable(_registers ?? const {}); + + /// The language of the stacktrace + String? lang; + + /// Indicates that this stack trace is a snapshot triggered + /// by an external signal. + /// + /// If this field is false, then the stack trace points to the code that + /// caused this stack trace to be created. + /// This can be the location of a raised exception, as well as an exception or + /// signal handler. + /// + /// If this field is true, then the stack trace was captured as part + /// of creating an unrelated event. For example, a thread other than the + /// crashing thread, or a stack trace computed as a result of an external kill + /// signal. + bool? snapshot; + + @internal + String? baseAddr; + + @internal + String? buildId; + + @internal + final Map? unknown; + + /// Deserializes a [SentryStackTrace] from JSON [Map]. + factory SentryStackTrace.fromJson(Map data) { + final json = AccessAwareMap(data); + final framesJson = json['frames'] as List?; + return SentryStackTrace( + frames: framesJson != null + ? framesJson + .map((frameJson) => SentryStackFrame.fromJson(frameJson)) + .toList() + : [], + registers: json['registers'], + lang: json['lang'], + snapshot: json['snapshot'], + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + if (_frames?.isNotEmpty ?? false) + 'frames': + _frames?.map((frame) => frame.toJson()).toList(growable: false), + if (_registers?.isNotEmpty ?? false) 'registers': _registers, + if (lang != null) 'lang': lang, + if (snapshot != null) 'snapshot': snapshot, + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryStackTrace copyWith({ + List? frames, + Map? registers, + String? lang, + bool? snapshot, + }) => + SentryStackTrace( + frames: frames ?? this.frames, + registers: registers ?? this.registers, + lang: lang ?? this.lang, + snapshot: snapshot ?? this.snapshot, + unknown: unknown, + baseAddr: baseAddr, + buildId: buildId, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_thread.dart b/packages/dart/lib/src/protocol/sentry_thread.dart new file mode 100644 index 0000000000..828ab969a4 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_thread.dart @@ -0,0 +1,82 @@ +import 'package:meta/meta.dart'; + +import 'sentry_stack_trace.dart'; +import 'access_aware_map.dart'; + +/// The Threads Interface specifies threads that were running at the time an +/// event happened. These threads can also contain stack traces. +/// See https://develop.sentry.dev/sdk/event-payloads/threads/ +class SentryThread { + SentryThread({ + this.id, + this.name, + this.crashed, + this.current, + this.stacktrace, + this.unknown, + }); + + factory SentryThread.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryThread( + id: json['id'] as int?, + name: json['name'] as String?, + crashed: json['crashed'] as bool?, + current: json['current'] as bool?, + stacktrace: + json['stacktrace'] == null ? null : SentryStackTrace.fromJson(json), + unknown: json.notAccessed(), + ); + } + + /// The Id of the thread. + int? id; + + /// The name of the thread. + /// On Dart platforms where Isolates are available, this can be set to + /// [Isolate.debugName](https://api.flutter.dev/flutter/dart-isolate/Isolate/debugName.html) + String? name; + + /// Whether the crash happened on this thread. + bool? crashed; + + /// An optional flag to indicate that the thread was in the foreground. + bool? current; + + /// Stack trace. + /// See https://develop.sentry.dev/sdk/event-payloads/stacktrace/ + SentryStackTrace? stacktrace; + + @internal + final Map? unknown; + + Map toJson() { + final stacktrace = this.stacktrace; + return { + ...?unknown, + if (id != null) 'id': id, + if (name != null) 'name': name, + if (crashed != null) 'crashed': crashed, + if (current != null) 'current': current, + if (stacktrace != null) 'stacktrace': stacktrace.toJson(), + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryThread copyWith({ + int? id, + String? name, + bool? crashed, + bool? current, + SentryStackTrace? stacktrace, + }) { + return SentryThread( + id: id ?? this.id, + name: name ?? this.name, + crashed: crashed ?? this.crashed, + current: current ?? this.current, + stacktrace: stacktrace ?? this.stacktrace, + unknown: unknown, + ); + } +} diff --git a/packages/dart/lib/src/protocol/sentry_trace_context.dart b/packages/dart/lib/src/protocol/sentry_trace_context.dart new file mode 100644 index 0000000000..96f806ba39 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_trace_context.dart @@ -0,0 +1,128 @@ +import 'package:meta/meta.dart'; + +import '../../sentry.dart'; +import '../propagation_context.dart'; +import '../protocol.dart'; +import 'access_aware_map.dart'; + +class SentryTraceContext { + static const String type = 'trace'; + + /// Determines which trace the Span belongs to + final SentryId traceId; + + /// Span id + final SpanId spanId; + + /// Id of a parent span + SpanId? parentSpanId; + + /// Replay associated with this trace. + SentryId? replayId; + + /// Whether the span is sampled or not + bool? sampled; + + /// Short code identifying the type of operation the span is measuring + String operation; + + /// Longer description of the span's operation, which uniquely identifies the span but is + /// consistent across instances of the span. + String? description; + + /// The Span status + SpanStatus? status; + + /// The origin of the span indicates what created the span. + /// + /// @note Gets set by the SDK. It is not expected to be set manually by users. + /// + /// @see + String? origin; + + Map? data; + + @internal + final Map? unknown; + + factory SentryTraceContext.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryTraceContext( + operation: json['op'] as String, + spanId: SpanId.fromId(json['span_id'] as String), + parentSpanId: json['parent_span_id'] == null + ? null + : SpanId.fromId(json['parent_span_id'] as String), + traceId: SentryId.fromId(json['trace_id'] as String), + replayId: json['replay_id'] == null + ? null + : SentryId.fromId(json['replay_id'] as String), + description: json['description'] as String?, + status: json['status'] == null + ? null + : SpanStatus.fromString(json['status'] as String), + sampled: true, + origin: json['origin'] == null ? null : json['origin'] as String?, + data: json['data'] == null ? null : json['data'] as Map, + unknown: json.notAccessed(), + ); + } + + /// Item encoded as JSON + Map toJson() { + return { + ...?unknown, + 'span_id': spanId.toString(), + 'trace_id': traceId.toString(), + 'op': operation, + if (parentSpanId != null) 'parent_span_id': parentSpanId!.toString(), + if (replayId != null) 'replay_id': replayId!.toString(), + if (description != null) 'description': description, + if (status != null) 'status': status!.toString(), + if (origin != null) 'origin': origin, + if (data != null) 'data': data, + }; + } + + @Deprecated('Will be removed in a future version.') + SentryTraceContext clone() => SentryTraceContext( + operation: operation, + traceId: traceId, + spanId: spanId, + description: description, + status: status, + parentSpanId: parentSpanId, + sampled: sampled, + origin: origin, + unknown: unknown, + replayId: replayId, + data: data, + ); + + SentryTraceContext({ + SentryId? traceId, + SpanId? spanId, + this.parentSpanId, + this.sampled, + required this.operation, + this.description, + this.status, + this.origin, + this.unknown, + this.replayId, + this.data, + }) : traceId = traceId ?? SentryId.newId(), + spanId = spanId ?? SpanId.newId(); + + @internal + factory SentryTraceContext.fromPropagationContext( + PropagationContext propagationContext) { + return SentryTraceContext( + traceId: propagationContext.traceId, + spanId: SpanId.newId(), + operation: 'default', + sampled: propagationContext.sampled, + replayId: propagationContext.baggage?.getReplayId(), + ); + } +} diff --git a/packages/dart/lib/src/protocol/sentry_trace_header.dart b/packages/dart/lib/src/protocol/sentry_trace_header.dart new file mode 100644 index 0000000000..a4aebe3416 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_trace_header.dart @@ -0,0 +1,51 @@ +import 'package:meta/meta.dart'; + +import '../invalid_sentry_trace_header_exception.dart'; +import '../protocol.dart'; + +/// Represents HTTP header "sentry-trace". +@immutable +class SentryTraceHeader { + static const _traceHeader = 'sentry-trace'; + + final SentryId traceId; + final SpanId spanId; + final bool? sampled; + + String get name => _traceHeader; + + String get value { + if (sampled != null) { + final sampled = this.sampled! ? '1' : '0'; + return '$traceId-$spanId-$sampled'; + } else { + return '$traceId-$spanId'; + } + } + + SentryTraceHeader( + this.traceId, + this.spanId, { + this.sampled, + }); + + factory SentryTraceHeader.fromTraceHeader(String header) { + final parts = header.split('-'); + bool? sampled; + + if (parts.length < 2) { + throw InvalidSentryTraceHeaderException('Header: $header is invalid.'); + } else if (parts.length == 3) { + sampled = '1' == parts[2]; + } + + final traceId = SentryId.fromId(parts[0]); + final spanId = SpanId.fromId(parts[1]); + + return SentryTraceHeader( + traceId, + spanId, + sampled: sampled, + ); + } +} diff --git a/packages/dart/lib/src/protocol/sentry_transaction.dart b/packages/dart/lib/src/protocol/sentry_transaction.dart new file mode 100644 index 0000000000..0dff8c151e --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_transaction.dart @@ -0,0 +1,155 @@ +import 'package:meta/meta.dart'; + +import '../protocol.dart'; +import '../sentry_measurement.dart'; +import '../sentry_tracer.dart'; +import '../utils.dart'; + +class SentryTransaction extends SentryEvent { + late final DateTime startTimestamp; + static const String _type = 'transaction'; + late final List spans; + @internal + final SentryTracer tracer; + late final Map measurements; + late final SentryTransactionInfo? transactionInfo; + + SentryTransaction( + this.tracer, { + super.eventId, + DateTime? timestamp, + super.platform, + super.serverName, + super.release, + super.dist, + super.environment, + String? transaction, + dynamic throwable, + Map? tags, + @Deprecated( + 'Additional Data is deprecated in favor of structured [Contexts] and should be avoided when possible') + Map? extra, + super.user, + super.contexts, + super.breadcrumbs, + super.sdk, + super.request, + String? type, + Map? measurements, + SentryTransactionInfo? transactionInfo, + }) : super( + timestamp: timestamp ?? tracer.endTimestamp, + transaction: transaction ?? tracer.name, + throwable: throwable ?? tracer.throwable, + tags: tags ?? tracer.tags, + // ignore: deprecated_member_use_from_same_package + extra: extra ?? tracer.data, + type: _type, + ) { + startTimestamp = tracer.startTimestamp; + + final spanContext = tracer.context; + spans = tracer.children; + this.measurements = measurements ?? {}; + + final data = extra ?? tracer.data; + contexts.trace = spanContext.toTraceContext( + sampled: tracer.samplingDecision?.sampled, + status: tracer.status, + data: data.isEmpty ? null : data, + ); + + this.transactionInfo = transactionInfo ?? + SentryTransactionInfo(tracer.transactionNameSource.name); + } + + @override + Map toJson() { + final json = super.toJson(); + + if (spans.isNotEmpty) { + json['spans'] = spans.map((e) => e.toJson()).toList(growable: false); + } + json['start_timestamp'] = + formatDateAsIso8601WithMillisPrecision(startTimestamp); + + if (measurements.isNotEmpty) { + final map = {}; + for (final item in measurements.entries) { + map[item.key] = item.value.toJson(); + } + json['measurements'] = map; + } + + final transactionInfo = this.transactionInfo; + if (transactionInfo != null) { + json['transaction_info'] = transactionInfo.toJson(); + } + + return json; + } + + bool get finished => timestamp != null; + + bool get sampled => contexts.trace?.sampled == true; + + @Deprecated('Assign values directly to the instance.') + @override + SentryTransaction copyWith({ + SentryId? eventId, + DateTime? timestamp, + String? platform, + String? logger, + String? serverName, + String? release, + String? dist, + String? environment, + Map? modules, + SentryMessage? message, + String? transaction, + dynamic throwable, + SentryLevel? level, + String? culprit, + Map? tags, + @Deprecated( + 'Additional Data is deprecated in favor of structured [Contexts] and should be avoided when possible') + Map? extra, + List? fingerprint, + SentryUser? user, + Contexts? contexts, + List? breadcrumbs, + SdkVersion? sdk, + SentryRequest? request, + DebugMeta? debugMeta, + List? exceptions, + List? threads, + String? type, + Map? measurements, + SentryTransactionInfo? transactionInfo, + }) => + SentryTransaction( + tracer, + eventId: eventId ?? this.eventId, + timestamp: timestamp ?? this.timestamp, + platform: platform ?? this.platform, + serverName: serverName ?? this.serverName, + release: release ?? this.release, + dist: dist ?? this.dist, + environment: environment ?? this.environment, + transaction: transaction ?? this.transaction, + throwable: throwable ?? this.throwable, + tags: (tags != null ? Map.from(tags) : null) ?? this.tags, + // ignore: deprecated_member_use_from_same_package + extra: (extra != null ? Map.from(extra) : null) ?? this.extra, + user: user ?? this.user, + contexts: contexts ?? this.contexts, + breadcrumbs: (breadcrumbs != null ? List.from(breadcrumbs) : null) ?? + this.breadcrumbs, + sdk: sdk ?? this.sdk, + request: request ?? this.request, + type: type ?? this.type, + measurements: (measurements != null ? Map.from(measurements) : null) ?? + this.measurements, + transactionInfo: transactionInfo ?? this.transactionInfo, + ); +} diff --git a/packages/dart/lib/src/protocol/sentry_transaction_info.dart b/packages/dart/lib/src/protocol/sentry_transaction_info.dart new file mode 100644 index 0000000000..a70e536375 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_transaction_info.dart @@ -0,0 +1,37 @@ +import 'package:meta/meta.dart'; + +import 'access_aware_map.dart'; + +class SentryTransactionInfo { + SentryTransactionInfo(this.source, {this.unknown}); + + final String source; + + @internal + final Map? unknown; + + Map toJson() { + return { + ...?unknown, + 'source': source, + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryTransactionInfo copyWith({ + String? source, + }) { + return SentryTransactionInfo( + source ?? this.source, + unknown: unknown, + ); + } + + factory SentryTransactionInfo.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryTransactionInfo( + json['source'], + unknown: json.notAccessed(), + ); + } +} diff --git a/packages/dart/lib/src/protocol/sentry_transaction_name_source.dart b/packages/dart/lib/src/protocol/sentry_transaction_name_source.dart new file mode 100644 index 0000000000..278d1757a5 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_transaction_name_source.dart @@ -0,0 +1,19 @@ +enum SentryTransactionNameSource { + /// User-defined name + custom, + + /// Raw URL, potentially containing identifiers. + url, + + /// Parametrized URL / route + route, + + /// Name of the view handling the request. + view, + + /// Named after a software component, such as a function or class name. + component, + + /// Name of a background task + task, +} diff --git a/packages/dart/lib/src/protocol/sentry_user.dart b/packages/dart/lib/src/protocol/sentry_user.dart new file mode 100644 index 0000000000..2b6f5f4f95 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_user.dart @@ -0,0 +1,164 @@ +import 'package:meta/meta.dart'; + +import '../../sentry.dart'; +import 'access_aware_map.dart'; + +/// Describes the current user associated with the application, such as the +/// currently signed in user. +/// +/// The user can be specified globally in the [Scope.user] field, +/// or per event in the [SentryEvent.user] field. +/// +/// You should provide at least one of [id], [email], [ipAddress], [username] +/// for Sentry to be able to tell you how many users are affected by one +/// issue, for example. Sending a user that has none of these attributes and +/// only custom attributes is valid, but not as useful. +/// +/// Conforms to the User Interface contract for Sentry +/// https://develop.sentry.dev/sdk/event-payloads/user/ +/// +/// The outgoing JSON representation is: +/// +/// ``` +/// "user": { +/// "id": "unique_id", +/// "username": "my_user", +/// "email": "foo@example.com", +/// "ip_address": "127.0.0.1", +/// } +/// ``` +class SentryUser { + /// You should provide at least one of [id], [email], [ipAddress], [username] + /// for Sentry to be able to tell you how many users are affected by one + /// issue, for example. Sending a user that has none of these attributes and + /// only custom attributes is valid, but not as useful. + SentryUser({ + this.id, + this.username, + this.email, + this.ipAddress, + this.geo, + this.name, + Map? data, + @Deprecated('Will be removed in v8. Use [data] instead') + Map? extras, + this.unknown, + }) : assert(id != null || + username != null || + email != null || + ipAddress != null), + data = data == null ? null : Map.from(data), + // ignore: deprecated_member_use_from_same_package + extras = extras == null ? null : Map.from(extras); + + /// A unique identifier of the user. + String? id; + + /// The username of the user. + String? username; + + /// The email address of the user. + String? email; + + /// The IP of the user. + String? ipAddress; + + /// Any other user context information that may be helpful. + /// + /// These keys are stored as extra information but not specifically processed + /// by Sentry. + Map? data; + + @Deprecated('Will be removed in v8. Use [data] instead') + Map? extras; + + /// Approximate geographical location of the end user or device. + /// + /// The geolocation is automatically inferred by Sentry.io if the [ipAddress] is set. + /// Sentry however doesn't collect the [ipAddress] automatically because it is PII. + /// The geo location will currently not be synced to the native layer, if available. + // See https://github.com/getsentry/sentry-dart/issues/1065 + SentryGeo? geo; + + /// Human readable name of the user. + String? name; + + @internal + final Map? unknown; + + /// Deserializes a [SentryUser] from JSON [Map]. + factory SentryUser.fromJson(Map jsonData) { + final json = AccessAwareMap(jsonData); + + var extras = json['extras']; + if (extras != null) { + extras = Map.from(extras); + } + + var data = json['data']; + if (data != null) { + data = Map.from(data); + } + + SentryGeo? geo; + final geoJson = json['geo']; + if (geoJson != null) { + geo = SentryGeo.fromJson(Map.from(geoJson)); + } + return SentryUser( + id: json['id'], + username: json['username'], + email: json['email'], + ipAddress: json['ip_address'], + data: data, + geo: geo, + name: json['name'], + // ignore: deprecated_member_use_from_same_package + extras: extras, + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + final geoJson = geo?.toJson(); + return { + ...?unknown, + if (id != null) 'id': id, + if (username != null) 'username': username, + if (email != null) 'email': email, + if (ipAddress != null) 'ip_address': ipAddress, + if (data?.isNotEmpty ?? false) 'data': data, + // ignore: deprecated_member_use_from_same_package + if (extras?.isNotEmpty ?? false) 'extras': extras, + if (name != null) 'name': name, + if (geoJson != null && geoJson.isNotEmpty) 'geo': geoJson, + }; + } + + @Deprecated('Assign values directly to the instance.') + SentryUser copyWith({ + String? id, + String? username, + String? email, + String? ipAddress, + @Deprecated('Will be removed in v8. Use [data] instead') + Map? extras, + String? name, + SentryGeo? geo, + Map? data, + }) { + return SentryUser( + id: id ?? this.id, + username: username ?? this.username, + email: email ?? this.email, + ipAddress: ipAddress ?? this.ipAddress, + data: data ?? this.data, + // ignore: deprecated_member_use_from_same_package + extras: extras ?? this.extras, + geo: geo ?? this.geo, + name: name ?? this.name, + unknown: unknown, + ); + } +} diff --git a/packages/dart/lib/src/protocol/sentry_view_hierarchy.dart b/packages/dart/lib/src/protocol/sentry_view_hierarchy.dart new file mode 100644 index 0000000000..f97410f7ac --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_view_hierarchy.dart @@ -0,0 +1,20 @@ +import 'package:meta/meta.dart'; + +import 'sentry_view_hierarchy_element.dart'; + +@immutable +class SentryViewHierarchy { + SentryViewHierarchy(this.renderingSystem); + + final String renderingSystem; + final List windows = []; + + /// Header encoded as JSON + Map toJson() { + return { + 'rendering_system': renderingSystem, + if (windows.isNotEmpty) + 'windows': windows.map((e) => e.toJson()).toList(growable: false), + }; + } +} diff --git a/packages/dart/lib/src/protocol/sentry_view_hierarchy_element.dart b/packages/dart/lib/src/protocol/sentry_view_hierarchy_element.dart new file mode 100644 index 0000000000..5a4ae777e4 --- /dev/null +++ b/packages/dart/lib/src/protocol/sentry_view_hierarchy_element.dart @@ -0,0 +1,55 @@ +import 'package:meta/meta.dart'; + +@immutable +class SentryViewHierarchyElement { + SentryViewHierarchyElement( + this.type, { + this.depth, + this.identifier, + this.width, + this.height, + this.x, + this.y, + this.z, + this.visible, + this.alpha, + this.extra, + }); + + final String type; + final int? depth; + final String? identifier; + final List children = []; + final double? width; + final double? height; + final double? x; + final double? y; + final double? z; + final bool? visible; + final double? alpha; + final Map? extra; + + /// Header encoded as JSON + Map toJson() { + final jsonMap = { + 'type': type, + if (depth != null) 'depth': depth, + if (identifier != null) 'identifier': identifier, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (x != null) 'x': x, + if (y != null) 'y': y, + if (z != null) 'z': z, + if (visible != null) 'visible': visible, + if (alpha != null) 'alpha': alpha, + if (children.isNotEmpty) + 'children': children.map((e) => e.toJson()).toList(growable: false), + }; + + if (extra?.isNotEmpty ?? false) { + jsonMap.addAll(extra!); + } + + return jsonMap; + } +} diff --git a/packages/dart/lib/src/protocol/span_id.dart b/packages/dart/lib/src/protocol/span_id.dart new file mode 100644 index 0000000000..c72ba37957 --- /dev/null +++ b/packages/dart/lib/src/protocol/span_id.dart @@ -0,0 +1,38 @@ +import 'package:meta/meta.dart'; +import 'package:uuid/uuid.dart'; + +/// The length is exactly 16 characters. +/// Dashes are not allowed. Has to be lowercase. +@immutable +class SpanId { + final String _id; + + static final Uuid _uuidGenerator = Uuid(); + + SpanId._internal({String? id}) + : _id = id?.replaceAll('-', '') ?? + _uuidGenerator.v4().replaceAll('-', '').substring(0, 16); + + /// Generates a new SpanId + SpanId.newId() : this._internal(); + + /// Generates a SpanId with the given UUID + SpanId.fromId(String id) : this._internal(id: id); + + /// SpanId with an empty UUID + const SpanId.empty() : _id = '0000000000000000'; + + @override + String toString() => _id; + + @override + int get hashCode => _id.hashCode; + + @override + bool operator ==(o) { + if (o is SpanId) { + return o._id == _id; + } + return false; + } +} diff --git a/packages/dart/lib/src/protocol/span_status.dart b/packages/dart/lib/src/protocol/span_status.dart new file mode 100644 index 0000000000..007fe1cfd9 --- /dev/null +++ b/packages/dart/lib/src/protocol/span_status.dart @@ -0,0 +1,145 @@ +/// The Span statuses +class SpanStatus { + const SpanStatus._( + this._value, + this._minHttpStatusCode, { + int? maxHttpStatusCode, + }) : _maxHttpStatusCode = maxHttpStatusCode ?? _minHttpStatusCode; + + /// Not an error, returned on success. + const SpanStatus.ok() : this._('ok', 200, maxHttpStatusCode: 299); + + /// The operation was cancelled, typically by the caller. + const SpanStatus.cancelled() : this._('cancelled', 499); + + /// Some invariants expected by the underlying system have been broken. + /// This code is reserved for serious errors. + const SpanStatus.internalError() : this._('internal_error', 500); + + /// An unknown error raised by APIs that don't return enough error information. + const SpanStatus.unknown() : this._('unknown', 500); + + /// An unknown error raised by APIs that don't return enough error information. + const SpanStatus.unknownError() : this._('unknown_error', 500); + + /// The client specified an invalid argument. + const SpanStatus.invalidArgument() : this._('invalid_argument', 400); + + /// The deadline expired before the operation could succeed. + const SpanStatus.deadlineExceeded() : this._('deadline_exceeded', 504); + + /// Content was not found or request was denied for an entire class of users. + const SpanStatus.notFound() : this._('not_found', 404); + + /// The entity attempted to be created already exists + const SpanStatus.alreadyExists() : this._('already_exists', 409); + + /// The caller doesn't have permission to execute the specified operation. + const SpanStatus.permissionDenied() : this._('permission_denied', 403); + + /// The resource has been exhausted e.g. per-user quota exhausted, file system out of space. + const SpanStatus.resourceExhausted() : this._('resource_exhausted', 429); + + /// The client shouldn't retry until the system state has been explicitly handled. + const SpanStatus.failedPrecondition() : this._('failed_precondition', 400); + + /// The operation was aborted. + const SpanStatus.aborted() : this._('aborted', 409); + + /// The operation was attempted past the valid range e.g. seeking past the end of a file. + const SpanStatus.outOfRange() : this._('out_of_range', 400); + + /// The operation is not implemented or is not supported/enabled for this operation. + const SpanStatus.unimplemented() : this._('unimplemented', 501); + + /// The service is currently available e.g. as a transient condition. + const SpanStatus.unavailable() : this._('unavailable', 503); + + /// Unrecoverable data loss or corruption. + const SpanStatus.dataLoss() : this._('data_loss', 500); + + /// The requester doesn't have valid authentication credentials for the operation. + const SpanStatus.unauthenticated() : this._('unauthenticated', 401); + + final String _value; + final int _minHttpStatusCode; + final int _maxHttpStatusCode; + + @override + String toString() => _value; + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(o) { + if (o is SpanStatus) { + return o._value == _value && + o._minHttpStatusCode == _minHttpStatusCode && + o._maxHttpStatusCode == _maxHttpStatusCode; + } + return false; + } + + /// Creates SpanStatus from HTTP status code. + factory SpanStatus.fromHttpStatusCode( + int httpStatusCode, { + SpanStatus? fallback, + }) { + var status = SpanStatus.ok(); + if (_matches(status, httpStatusCode)) { + return status; + } + status = SpanStatus.cancelled(); + if (_matches(status, httpStatusCode)) { + return status; + } + status = SpanStatus.unknown(); + if (_matches(status, httpStatusCode)) { + return status; + } + status = SpanStatus.invalidArgument(); + if (_matches(status, httpStatusCode)) { + return status; + } + status = SpanStatus.deadlineExceeded(); + if (_matches(status, httpStatusCode)) { + return status; + } + status = SpanStatus.notFound(); + if (_matches(status, httpStatusCode)) { + return status; + } + status = SpanStatus.alreadyExists(); + if (_matches(status, httpStatusCode)) { + return status; + } + status = SpanStatus.permissionDenied(); + if (_matches(status, httpStatusCode)) { + return status; + } + status = SpanStatus.resourceExhausted(); + if (_matches(status, httpStatusCode)) { + return status; + } + status = SpanStatus.unimplemented(); + if (_matches(status, httpStatusCode)) { + return status; + } + status = SpanStatus.unavailable(); + if (_matches(status, httpStatusCode)) { + return status; + } + status = SpanStatus.unauthenticated(); + if (_matches(status, httpStatusCode)) { + return status; + } + return fallback ?? SpanStatus.unknownError(); + } + + /// Creates SpanStatus from a String. + factory SpanStatus.fromString(String value) => SpanStatus._(value, 0); + + static bool _matches(SpanStatus status, int code) => + code >= status._minHttpStatusCode && code <= status._maxHttpStatusCode; +} diff --git a/packages/dart/lib/src/recursive_exception_cause_extractor.dart b/packages/dart/lib/src/recursive_exception_cause_extractor.dart new file mode 100644 index 0000000000..f88b5f8fdc --- /dev/null +++ b/packages/dart/lib/src/recursive_exception_cause_extractor.dart @@ -0,0 +1,54 @@ +import 'sentry_options.dart'; +import 'throwable_mechanism.dart'; +import 'exception_cause.dart'; +import 'protocol.dart'; +import 'package:meta/meta.dart'; + +/// Extracts inner exceptions recursively +@internal +class RecursiveExceptionCauseExtractor { + RecursiveExceptionCauseExtractor(this._options); + + final SentryOptions _options; + + List flatten(dynamic exception, dynamic stackTrace) { + final allExceptionCauses = []; + final circularityDetector = {}; + + var currentException = exception; + ExceptionCause? currentExceptionCause = ExceptionCause( + exception, + stackTrace, + ); + + while (currentException != null && + currentExceptionCause != null && + circularityDetector.add(currentException)) { + allExceptionCauses.add(currentExceptionCause); + + final extractionSourceSource = currentException is ThrowableMechanism + ? currentException.throwable + : currentException; + + final extractor = + _options.exceptionCauseExtractor(extractionSourceSource.runtimeType); + + try { + currentExceptionCause = extractor?.cause(extractionSourceSource); + currentException = currentExceptionCause?.exception; + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'An exception occurred while extracting exception cause', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + break; + } + } + return allExceptionCauses; + } +} diff --git a/packages/dart/lib/src/run_zoned_guarded_integration.dart b/packages/dart/lib/src/run_zoned_guarded_integration.dart new file mode 100644 index 0000000000..8ea770653f --- /dev/null +++ b/packages/dart/lib/src/run_zoned_guarded_integration.dart @@ -0,0 +1,40 @@ +import 'dart:async'; + +import 'sentry_run_zoned_guarded.dart'; +import '../sentry.dart'; + +/// Called inside of `runZonedGuarded` +typedef RunZonedGuardedRunner = Future Function(); + +/// Caught exception and stacktrace in `runZonedGuarded` +typedef RunZonedGuardedOnError = FutureOr Function(Object, StackTrace); + +/// Integration that runs runner function within `runZonedGuarded` and capture +/// errors on the `runZonedGuarded` error handler. +/// See https://api.dart.dev/stable/dart-async/runZonedGuarded.html +/// +/// This integration also records calls to `print()` as Breadcrumbs. +/// This can be configured with [SentryOptions.enablePrintBreadcrumbs] +class RunZonedGuardedIntegration extends Integration { + RunZonedGuardedIntegration(this._runner, this._onError); + + final RunZonedGuardedRunner _runner; + final RunZonedGuardedOnError? _onError; + + @override + Future call(Hub hub, SentryOptions options) { + final completer = Completer(); + + SentryRunZonedGuarded.sentryRunZonedGuarded(hub, () async { + try { + await _runner(); + } finally { + completer.complete(); + } + }, _onError); + + options.sdk.addIntegration('runZonedGuardedIntegration'); + + return completer.future; + } +} diff --git a/packages/dart/lib/src/runtime_checker.dart b/packages/dart/lib/src/runtime_checker.dart new file mode 100644 index 0000000000..d458448b69 --- /dev/null +++ b/packages/dart/lib/src/runtime_checker.dart @@ -0,0 +1,51 @@ +import 'dart:async'; + +import 'utils/stacktrace_utils.dart'; + +/// Helper to check in which environment the library is running. +/// The environment checks (release/debug/profile) are mutually exclusive. +class RuntimeChecker { + RuntimeChecker({ + bool? isRootZone, + }) : isRootZone = isRootZone ?? Zone.current == Zone.root; + + /// Check if running in release/production environment + bool isReleaseMode() { + return const bool.fromEnvironment('dart.vm.product', defaultValue: false); + } + + /// Check if running in debug environment + bool isDebugMode() { + return !isReleaseMode() && !isProfileMode(); + } + + /// Check if running in profile environment + bool isProfileMode() { + return const bool.fromEnvironment('dart.vm.profile', defaultValue: false); + } + + /// Check if the Dart code is obfuscated. + bool isAppObfuscated() { + // In non-obfuscated builds, this will return "RuntimeChecker" + // In obfuscated builds, this will return something like "a" or other short identifier + // Note: Flutter Web production builds will always be minified / "obfuscated". + final typeName = runtimeType.toString(); + return !typeName.contains('RuntimeChecker'); + } + + /// Check if the current build has been built with --split-debug-info + bool isSplitDebugInfoBuild() { + final str = StackTrace.current.toString(); + return buildIdRegex.hasMatch(str) || absRegex.hasMatch(str); + } + + final bool isRootZone; + + String get compileMode { + return isReleaseMode() + ? 'release' + : isDebugMode() + ? 'debug' + : 'profile'; + } +} diff --git a/packages/dart/lib/src/scope.dart b/packages/dart/lib/src/scope.dart new file mode 100644 index 0000000000..fef52b5d9f --- /dev/null +++ b/packages/dart/lib/src/scope.dart @@ -0,0 +1,474 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:meta/meta.dart'; + +import 'event_processor.dart'; +import 'event_processor/run_event_processors.dart'; +import 'hint.dart'; +import 'propagation_context.dart'; +import 'protocol.dart'; +import 'scope_observer.dart'; +import 'sentry_attachment/sentry_attachment.dart'; +import 'sentry_options.dart'; +import 'sentry_span_interface.dart'; +import 'sentry_tracer.dart'; + +typedef _OnScopeObserver = Future Function(ScopeObserver observer); + +/// Scope data to be sent with the event +class Scope { + /// How important this event is. + SentryLevel? level; + + String? _transaction; + + /// The name of the transaction which generated this event, + /// for example, the route name: `"/users//"`. + String? get transaction { + return ((span is SentryTracer) ? (span as SentryTracer?)?.name : null) ?? + _transaction; + } + + set transaction(String? transaction) { + _transaction = transaction; + + if (_transaction != null && span != null) { + final currentTransaction = + (span is SentryTracer) ? (span as SentryTracer?) : null; + currentTransaction?.name = _transaction!; + } + } + + /// Returns active transaction or null if there is no active transaction. + ISentrySpan? span; + + /// The propagation context for connecting errors and spans to traces. + /// There should always be a propagation context available at all times. + /// + /// Default behaviour of trace generation in Flutter: + /// + /// If `SentryNavigatorObserver` is available: + /// - new trace on navigation for all platforms + /// + /// if `SentryNavigatorObserver` is not available: + /// - Mobile: traces will be cycled with the background/foreground hooks, similar to how sessions are defined in mobile + /// - Web: traces will stick until the next refresh (this might change in the future) + @internal + PropagationContext propagationContext = PropagationContext(); + + SentryUser? _user; + + /// Get the current user. + SentryUser? get user => _user; + + void _setUserSync(SentryUser? user) { + _user = user; + } + + /// Set the current user. + Future setUser(SentryUser? user) async { + _setUserSync(user); + await _callScopeObservers( + (scopeObserver) async => await scopeObserver.setUser(user)); + } + + List _fingerprint = []; + + /// Used to deduplicate events by grouping ones with the same fingerprint + /// together. + /// + /// Example: + /// + /// // A completely custom fingerprint: + /// var custom = ['foo', 'bar', 'baz']; + List get fingerprint => List.unmodifiable(_fingerprint); + + set fingerprint(List fingerprint) { + _fingerprint = List.from(fingerprint); + } + + /// List of breadcrumbs for this scope. + final Queue _breadcrumbs = Queue(); + + /// Unmodifiable List of breadcrumbs + /// See also: + /// * https://docs.sentry.io/enriching-error-data/breadcrumbs/?platform=javascript + List get breadcrumbs => List.unmodifiable(_breadcrumbs); + + final Map _tags = {}; + + /// Name/value pairs that events can be searched by. + Map get tags => Map.unmodifiable(_tags); + + final Map _extra = {}; + + /// Arbitrary name/value pairs attached to the scope. + /// + /// Sentry.io docs do not talk about restrictions on the values, other than + /// they must be JSON-serializable. + Map get extra => Map.unmodifiable(_extra); + + /// Active replay recording. + @internal + SentryId? get replayId => _replayId; + @internal + set replayId(SentryId? value) => _replayId = value; + SentryId? _replayId; + + final Contexts _contexts = Contexts(); + + /// Unmodifiable map of the scope contexts key/value + /// See also: + /// * https://docs.sentry.io/platforms/java/enriching-events/context/ + Map get contexts => Map.unmodifiable(_contexts); + + void _setContextsSync(String key, dynamic value) { + // if it's a List, it should not be a List because it can't + // be wrapped by the value object since it's a special property for having + // multiple runtimes and it has a dedicated property within the Contexts class. + _contexts[key] = (value is num || + value is bool || + value is String || + (value is List && + (value is! List && + key != SentryRuntime.listType))) + ? {'value': value} + : value; + } + + /// add an entry to the Scope's contexts + Future setContexts(String key, dynamic value) async { + _setContextsSync(key, value); + await _callScopeObservers( + (scopeObserver) async => await scopeObserver.setContexts(key, value)); + } + + /// Removes a value from the Scope's contexts + Future removeContexts(String key) async { + _contexts.remove(key); + + await _callScopeObservers( + (scopeObserver) async => await scopeObserver.removeContexts(key)); + } + + /// Scope's event processor list + /// + /// Scope's event processors are executed before the global Event processors + final List _eventProcessors = []; + + List get eventProcessors => + List.unmodifiable(_eventProcessors); + + final SentryOptions _options; + bool _enableScopeSync = true; + + final List _attachments = []; + + List get attachments => List.unmodifiable(_attachments); + + Scope(this._options); + + Breadcrumb? _addBreadCrumbSync(Breadcrumb breadcrumb, Hint hint) { + // bail out if maxBreadcrumbs is zero + if (_options.maxBreadcrumbs == 0) { + return null; + } + + Breadcrumb? processedBreadcrumb = breadcrumb; + // run before breadcrumb callback if set + if (_options.beforeBreadcrumb != null) { + try { + processedBreadcrumb = _options.beforeBreadcrumb!( + processedBreadcrumb, + hint, + ); + if (processedBreadcrumb == null) { + _options.log( + SentryLevel.info, + 'Breadcrumb was dropped by beforeBreadcrumb', + ); + return null; + } + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'The BeforeBreadcrumb callback threw an exception', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } + } + if (processedBreadcrumb != null) { + // remove first item if list is full + if (_breadcrumbs.length >= _options.maxBreadcrumbs && + _breadcrumbs.isNotEmpty) { + _breadcrumbs.removeFirst(); + } + _breadcrumbs.add(processedBreadcrumb); + } + return processedBreadcrumb; + } + + /// Adds a breadcrumb to the breadcrumbs queue + Future addBreadcrumb(Breadcrumb breadcrumb, {Hint? hint}) async { + final addedBreadcrumb = _addBreadCrumbSync(breadcrumb, hint ?? Hint()); + if (addedBreadcrumb != null) { + await _callScopeObservers((scopeObserver) async => + await scopeObserver.addBreadcrumb(addedBreadcrumb)); + } + } + + void addAttachment(SentryAttachment attachment) { + _attachments.add(attachment); + } + + void clearAttachments() { + _attachments.clear(); + } + + void _clearBreadcrumbsSync() { + _breadcrumbs.clear(); + } + + /// Clear all the breadcrumbs + Future clearBreadcrumbs() async { + _clearBreadcrumbsSync(); + await _callScopeObservers( + (scopeObserver) async => await scopeObserver.clearBreadcrumbs()); + } + + /// Adds an event processor + void addEventProcessor(EventProcessor eventProcessor) { + _eventProcessors.add(eventProcessor); + } + + /// Resets the Scope to its default state + Future clear() async { + clearAttachments(); + level = null; + span = null; + _transaction = null; + _fingerprint = []; + _tags.clear(); + _extra.clear(); + _eventProcessors.clear(); + _replayId = null; + propagationContext = PropagationContext(); + + _clearBreadcrumbsSync(); + _setUserSync(null); + + await clearBreadcrumbs(); + await setUser(null); + } + + void _setTagSync(String key, String value) { + _tags[key] = value; + } + + /// Sets a tag to the Scope + Future setTag(String key, String value) async { + _setTagSync(key, value); + await _callScopeObservers( + (scopeObserver) async => await scopeObserver.setTag(key, value)); + } + + /// Removes a tag from the Scope + Future removeTag(String key) async { + _tags.remove(key); + await _callScopeObservers( + (scopeObserver) async => await scopeObserver.removeTag(key)); + } + + void _setExtraSync(String key, dynamic value) { + _extra[key] = value; + } + + /// Sets an extra to the Scope + @Deprecated( + 'Use Contexts instead. Additional data is deprecated in favor of structured Contexts and should be avoided when possible') + Future setExtra(String key, dynamic value) async { + _setExtraSync(key, value); + await _callScopeObservers( + (scopeObserver) async => await scopeObserver.setExtra(key, value)); + } + + /// Removes an extra from the Scope + @Deprecated( + 'Use Contexts instead. Additional data is deprecated in favor of structured Contexts and should be avoided when possible') + Future removeExtra(String key) async { + _extra.remove(key); + await _callScopeObservers( + (scopeObserver) async => await scopeObserver.removeExtra(key)); + } + + Future applyToEvent( + SentryEvent event, + Hint hint, + ) async { + event + ..transaction = event.transaction ?? transaction + ..user = _mergeUsers(user, event.user) + ..tags = tags.isNotEmpty ? _mergeEventTags(event) : event.tags; + + if (event.type != 'feedback') { + event.breadcrumbs = (event.breadcrumbs?.isNotEmpty ?? false) + ? event.breadcrumbs + : List.from(_breadcrumbs); + // ignore: deprecated_member_use_from_same_package + event.extra = extra.isNotEmpty ? _mergeEventExtra(event) : event.extra; + } + + if (event is! SentryTransaction) { + event + ..fingerprint = (event.fingerprint?.isNotEmpty ?? false) + ? event.fingerprint + : _fingerprint + ..level = level ?? event.level; + } + + _contexts.forEach((key, value) { + // add the contexts runtime list to the event.contexts.runtimes + if (key == SentryRuntime.listType && + value is List && + value.isNotEmpty) { + _mergeEventContextsRuntimes(value, event); + } else if (key != SentryRuntime.listType && + (!event.contexts.containsKey(key) || event.contexts[key] == null) && + value != null) { + event.contexts[key] = value; + } + }); + + final newSpan = span; + if (event.contexts.trace == null) { + if (newSpan != null) { + event.contexts.trace = newSpan.context.toTraceContext( + sampled: newSpan.samplingDecision?.sampled, + ); + } else { + event.contexts.trace = + SentryTraceContext.fromPropagationContext(propagationContext); + } + } + + return await runEventProcessors(event, hint, _eventProcessors, _options); + } + + /// Merge the scope contexts runtimes and the event contexts runtimes. + void _mergeEventContextsRuntimes( + List values, SentryEvent event) { + for (final runtime in values) { + event.contexts.addRuntime(runtime); + } + } + + /// If the scope and the event have tag entries with the same key, + /// the event tags will be kept. + Map _mergeEventTags(SentryEvent event) => + tags.map((key, value) => MapEntry(key, value))..addAll(event.tags ?? {}); + + /// If the scope and the event have extra entries with the same key, + /// the event extra will be kept. + Map _mergeEventExtra(SentryEvent event) => + extra.map((key, value) => MapEntry(key, value)) + // ignore: deprecated_member_use_from_same_package + ..addAll(event.extra ?? {}); + + /// If scope and event have a user, the user of the event takes + /// precedence. + SentryUser? _mergeUsers(SentryUser? scopeUser, SentryUser? eventUser) { + if (scopeUser == null && eventUser != null) { + return eventUser; + } + if (eventUser == null && scopeUser != null) { + return scopeUser; + } + // otherwise the user of scope takes precedence over the event user + return scopeUser + ?..id = eventUser?.id + ..email = eventUser?.email + ..ipAddress = eventUser?.ipAddress + ..username = eventUser?.username + ..data = _mergeUserData(eventUser?.data, scopeUser.data) + // ignore: deprecated_member_use_from_same_package + ..extras = _mergeUserData(eventUser?.extras, scopeUser.extras); + } + + /// If the User on the scope and the user of an event have extra entries with + /// the same key, the event user extra will be kept. + Map _mergeUserData( + Map? eventData, + Map? scopeData, + ) { + final map = {}; + if (eventData != null) { + map.addAll(eventData); + } + if (scopeData == null) { + return map; + } + for (var value in scopeData.entries) { + map.putIfAbsent(value.key, () => value.value); + } + return map; + } + + /// Clones the current Scope + Scope clone() { + final clone = Scope(_options) + ..level = level + ..fingerprint = List.from(fingerprint) + .._transaction = _transaction + ..span = span + .._enableScopeSync = false + ..propagationContext = propagationContext + .._replayId = _replayId; + + clone._setUserSync(user); + + final tags = List.from(_tags.keys); + for (final tag in tags) { + final value = _tags[tag]; + if (value != null) { + clone._setTagSync(tag, value); + } + } + + for (final extraKey in List.from(_extra.keys)) { + clone._setExtraSync(extraKey, _extra[extraKey]); + } + + for (final breadcrumb in List.from(_breadcrumbs)) { + clone._addBreadCrumbSync(breadcrumb, Hint()); + } + + for (final eventProcessor in List.from(_eventProcessors)) { + clone.addEventProcessor(eventProcessor); + } + + for (final entry in Map.from(contexts).entries) { + if (entry.value != null) { + clone._setContextsSync(entry.key, entry.value); + } + } + + for (final attachment in List.from(_attachments)) { + clone.addAttachment(attachment); + } + + return clone; + } + + Future _callScopeObservers(_OnScopeObserver action) async { + if (_options.enableScopeSync && _enableScopeSync) { + for (final scopeObserver in _options.scopeObservers) { + await action(scopeObserver); + } + } + } +} diff --git a/packages/dart/lib/src/scope_observer.dart b/packages/dart/lib/src/scope_observer.dart new file mode 100644 index 0000000000..09af0c46a0 --- /dev/null +++ b/packages/dart/lib/src/scope_observer.dart @@ -0,0 +1,16 @@ +import 'dart:async'; + +import 'protocol/breadcrumb.dart'; +import 'protocol/sentry_user.dart'; + +abstract class ScopeObserver { + Future setContexts(String key, dynamic value); + Future removeContexts(String key); + Future setUser(SentryUser? user); + Future addBreadcrumb(Breadcrumb breadcrumb); + Future clearBreadcrumbs(); + Future setExtra(String key, dynamic value); + Future removeExtra(String key); + Future setTag(String key, String value); + Future removeTag(String key); +} diff --git a/packages/dart/lib/src/sdk_lifecycle_hooks.dart b/packages/dart/lib/src/sdk_lifecycle_hooks.dart new file mode 100644 index 0000000000..c5ffd6e018 --- /dev/null +++ b/packages/dart/lib/src/sdk_lifecycle_hooks.dart @@ -0,0 +1,73 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; + +import '../sentry.dart'; + +@internal +typedef SdkLifecycleCallback = FutureOr + Function(T event); + +@internal +abstract class SdkLifecycleEvent {} + +/// Holds and dispatches SDK lifecycle events in a type-safe way. +/// These are meant to be used internally and are not part of public api. +@internal +class SdkLifecycleRegistry { + SdkLifecycleRegistry(this._options); + + final SentryOptions _options; + final _lifecycleCallbacks = >{}; + + Map> get lifecycleCallbacks => _lifecycleCallbacks; + + void registerCallback( + SdkLifecycleCallback callback) { + _lifecycleCallbacks[T] ??= []; + _lifecycleCallbacks[T]?.add(callback); + } + + void removeCallback( + SdkLifecycleCallback callback) { + final callbacks = _lifecycleCallbacks[T]; + callbacks?.remove(callback); + } + + FutureOr dispatchCallback(T event) async { + final callbacks = _lifecycleCallbacks[event.runtimeType] ?? []; + for (final cb in callbacks) { + try { + final result = (cb as SdkLifecycleCallback)(event); + if (result is Future) { + await result; + } + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'The SDK lifecycle callback threw an exception', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } + } + } +} + +@internal +class OnBeforeCaptureLog extends SdkLifecycleEvent { + OnBeforeCaptureLog(this.log); + + final SentryLog log; +} + +@internal +class OnBeforeSendEvent extends SdkLifecycleEvent { + OnBeforeSendEvent(this.event, this.hint); + + final SentryEvent event; + final Hint hint; +} diff --git a/packages/dart/lib/src/sentry.dart b/packages/dart/lib/src/sentry.dart new file mode 100644 index 0000000000..7b6d79abdf --- /dev/null +++ b/packages/dart/lib/src/sentry.dart @@ -0,0 +1,438 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; + +import 'dart_exception_type_identifier.dart'; +import 'environment/environment_variables.dart'; +import 'event_processor/deduplication_event_processor.dart'; +import 'event_processor/enricher/enricher_event_processor.dart'; +import 'event_processor/exception/exception_event_processor.dart'; +import 'event_processor/exception/exception_group_event_processor.dart'; +import 'hint.dart'; +import 'hub.dart'; +import 'hub_adapter.dart'; +import 'integration.dart'; +import 'load_dart_debug_images_integration.dart'; +import 'noop_hub.dart'; +import 'noop_isolate_error_integration.dart' + if (dart.library.io) 'isolate_error_integration.dart'; +import 'protocol.dart'; +import 'protocol/sentry_feedback.dart'; +import 'run_zoned_guarded_integration.dart'; +import 'sentry_attachment/sentry_attachment.dart'; +import 'sentry_client.dart'; +import 'sentry_options.dart'; +import 'sentry_run_zoned_guarded.dart'; +import 'tracing.dart'; +import 'transport/data_category.dart'; +import 'transport/task_queue.dart'; +import 'feature_flags_integration.dart'; +import 'sentry_logger.dart'; +import 'logs_enricher_integration.dart'; + +/// Configuration options callback +typedef OptionsConfiguration = FutureOr Function(SentryOptions); + +/// Runs a callback inside of the `runZonedGuarded` method, useful for running your `runApp(MyApp())` +typedef AppRunner = FutureOr Function(); + +/// Sentry SDK main entry point +class Sentry { + static Hub _hub = NoOpHub(); + static TaskQueue _taskQueue = NoOpTaskQueue(); + + Sentry._(); + + /// Initializes the SDK + /// passing a [AppRunner] callback allows to run the app within its own error + /// zone ([`runZonedGuarded`](https://api.dart.dev/stable/2.10.4/dart-async/runZonedGuarded.html)) + static Future init( + OptionsConfiguration optionsConfiguration, { + AppRunner? appRunner, + @internal bool callAppRunnerInRunZonedGuarded = true, + @internal RunZonedGuardedOnError? runZonedGuardedOnError, + @internal SentryOptions? options, + }) async { + final sentryOptions = options ?? SentryOptions(); + + await _initDefaultValues(sentryOptions); + + try { + final config = optionsConfiguration(sentryOptions); + if (config is Future) { + await config; + } + _taskQueue = DefaultTaskQueue( + sentryOptions.maxQueueSize, + sentryOptions.log, + sentryOptions.recorder, + ); + } catch (exception, stackTrace) { + sentryOptions.log( + SentryLevel.error, + 'Error in options configuration.', + exception: exception, + stackTrace: stackTrace, + ); + if (sentryOptions.automatedTestMode) { + rethrow; + } + } + + if (sentryOptions.dsn == null) { + throw ArgumentError('DSN is required.'); + } + + await _init(sentryOptions, appRunner, callAppRunnerInRunZonedGuarded, + runZonedGuardedOnError); + } + + static Future _initDefaultValues(SentryOptions options) async { + _setEnvironmentVariables(options); + + // Throws when running on the browser + if (!options.platform.isWeb) { + // catch any errors that may occur within the entry function, main() + // in the ‘root zone’ where all Dart programs start + options.addIntegrationByIndex(0, IsolateErrorIntegration()); + } + + if (options.runtimeChecker.isDebugMode()) { + options.debug = true; + options.log( + SentryLevel.debug, + 'Debug mode is enabled: Application is running in a debug environment.', + ); + } + + if (options.enableDartSymbolication) { + options.addIntegration(LoadDartDebugImagesIntegration()); + } + + options.addIntegration(FeatureFlagsIntegration()); + options.addIntegration(LogsEnricherIntegration()); + + options.addEventProcessor(EnricherEventProcessor(options)); + options.addEventProcessor(ExceptionEventProcessor(options)); + options.addEventProcessor(DeduplicationEventProcessor(options)); + + options.prependExceptionTypeIdentifier(DartExceptionTypeIdentifier()); + + // Added last to ensure all error events have correct parent/child relationships + options.addEventProcessor(ExceptionGroupEventProcessor(options)); + } + + /// This method reads available environment variables and uses them + /// accordingly. + /// To see which environment variables are available, see [EnvironmentVariables] + /// + /// The precedence of these options are also described on + /// https://docs.sentry.io/platforms/dart/configuration/options/ + static void _setEnvironmentVariables(SentryOptions options) { + final vars = options.environmentVariables; + options.dsn = options.dsn ?? vars.dsn; + + if (options.environment == null) { + var environment = vars.environmentForMode(options.runtimeChecker); + options.environment = vars.environment ?? environment; + } + + options.release = options.release ?? vars.release; + options.dist = options.dist ?? vars.dist; + } + + /// Initializes the SDK + static Future _init( + SentryOptions options, + AppRunner? appRunner, + bool callAppRunnerInRunZonedGuarded, + RunZonedGuardedOnError? runZonedGuardedOnError, + ) async { + if (isEnabled) { + options.log( + SentryLevel.warning, + 'Sentry has been already initialized. Previous configuration will be overwritten.', + ); + } + + // let's set the default values to options + if (await _setDefaultConfiguration(options)) { + final hub = _hub; + _hub = Hub(options); + await hub.close(); + } + + // execute integrations after hub being enabled + if (appRunner != null) { + if (callAppRunnerInRunZonedGuarded) { + var runIntegrationsAndAppRunner = () async { + final integrations = options.integrations + .where((i) => i is! RunZonedGuardedIntegration); + await _callIntegrations(integrations, options); + await appRunner(); + }; + + final runZonedGuardedIntegration = RunZonedGuardedIntegration( + runIntegrationsAndAppRunner, runZonedGuardedOnError); + options.addIntegrationByIndex(0, runZonedGuardedIntegration); + + // RunZonedGuardedIntegration will run other integrations and appRunner + // runZonedGuarded so all exception caught in the error handler are + // handled + await runZonedGuardedIntegration(HubAdapter(), options); + } else { + await _callIntegrations(options.integrations, options); + await appRunner(); + } + } else { + await _callIntegrations(options.integrations, options); + } + } + + static Future _callIntegrations( + Iterable integrations, SentryOptions options) async { + for (final integration in integrations) { + final execute = integration(HubAdapter(), options); + if (execute is Future) { + await execute; + } + } + } + + /// Reports an [event] to Sentry.io. + static Future captureEvent( + SentryEvent event, { + dynamic stackTrace, + Hint? hint, + ScopeCallback? withScope, + }) => + _taskQueue.enqueue( + () => _hub.captureEvent( + event, + stackTrace: stackTrace, + hint: hint, + withScope: withScope, + ), + SentryId.empty(), + event.type != null + ? DataCategory.fromItemType(event.type!) + : DataCategory.unknown); + + /// Reports the [throwable] and optionally its [stackTrace] to Sentry.io. + static Future captureException( + dynamic throwable, { + dynamic stackTrace, + Hint? hint, + SentryMessage? message, + ScopeCallback? withScope, + }) => + _taskQueue.enqueue( + () => _hub.captureException( + throwable, + stackTrace: stackTrace, + hint: hint, + message: message, + withScope: withScope, + ), + SentryId.empty(), + DataCategory.error, + ); + + /// Reports a [message] to Sentry.io. + static Future captureMessage( + String? message, { + SentryLevel? level = SentryLevel.info, + String? template, + List? params, + Hint? hint, + ScopeCallback? withScope, + }) => + _taskQueue.enqueue( + () => _hub.captureMessage( + message, + level: level, + template: template, + params: params, + hint: hint, + withScope: withScope, + ), + SentryId.empty(), + DataCategory.unknown, + ); + + /// Reports [SentryFeedback] to Sentry.io. + /// + /// Use [withScope] to add [SentryAttachment] to the feedback. + static Future captureFeedback( + SentryFeedback feedback, { + Hint? hint, + ScopeCallback? withScope, + }) => + _taskQueue.enqueue( + () => _hub.captureFeedback( + feedback, + hint: hint, + withScope: withScope, + ), + SentryId.empty(), + DataCategory.unknown, + ); + + /// Close the client SDK + static Future close() async { + final hub = _hub; + _hub = NoOpHub(); + await hub.close(); + } + + /// Check if the current Hub is enabled/active. + static bool get isEnabled => _hub.isEnabled; + + /// Last event id recorded by the current Hub + static SentryId get lastEventId => _hub.lastEventId; + + /// Adds a breadcrumb to the current Scope + static Future addBreadcrumb(Breadcrumb crumb, {Hint? hint}) => + _hub.addBreadcrumb(crumb, hint: hint); + + /// Configures the scope through the callback. + static FutureOr configureScope(ScopeCallback callback) => + _hub.configureScope(callback); + + /// Clones the current Hub + static Hub clone() => _hub.clone(); + + /// Binds a different client to the current hub + static void bindClient(SentryClient client) => _hub.bindClient(client); + + static Future _setDefaultConfiguration(SentryOptions options) async { + // if the DSN is empty, let's disable the SDK + if (options.dsn?.isEmpty ?? false) { + await close(); + return false; + } + + // try parsing the dsn + options.parsedDsn; + + return true; + } + + /// Creates a Transaction and returns the instance. + static ISentrySpan startTransaction( + String name, + String operation, { + String? description, + DateTime? startTimestamp, + bool? bindToScope, + bool? waitForChildren, + Duration? autoFinishAfter, + bool? trimEnd, + OnTransactionFinish? onFinish, + Map? customSamplingContext, + }) => + _hub.startTransaction( + name, + operation, + description: description, + startTimestamp: startTimestamp, + bindToScope: bindToScope, + waitForChildren: waitForChildren, + autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, + onFinish: onFinish, + customSamplingContext: customSamplingContext, + ); + + /// Creates a Transaction and returns the instance. + static ISentrySpan startTransactionWithContext( + SentryTransactionContext transactionContext, { + Map? customSamplingContext, + DateTime? startTimestamp, + bool? bindToScope, + bool? waitForChildren, + Duration? autoFinishAfter, + bool? trimEnd, + OnTransactionFinish? onFinish, + }) => + _hub.startTransactionWithContext( + transactionContext, + customSamplingContext: customSamplingContext, + startTimestamp: startTimestamp, + bindToScope: bindToScope, + waitForChildren: waitForChildren, + autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, + onFinish: onFinish, + ); + + /// Gets the current active transaction or span bound to the scope. + /// Returns `null` if performance is disabled in the options. + static ISentrySpan? getSpan() => _hub.getSpan(); + + static Future addFeatureFlag(String flag, dynamic result) async { + if (result is! bool) { + return; + } + + final featureFlagsIntegration = currentHub.options.integrations + .whereType() + .firstOrNull; + + if (featureFlagsIntegration == null) { + currentHub.options.log( + SentryLevel.warning, + '$FeatureFlagsIntegration not found. Make sure Sentry is initialized before accessing the addFeatureFlag API.', + ); + return; + } + + await featureFlagsIntegration.addFeatureFlag(flag, result); + } + + @internal + static Hub get currentHub => _hub; + + /// Creates a new error handling zone with Sentry integration using [runZonedGuarded]. + /// + /// This method provides automatic error reporting and breadcrumb tracking while + /// allowing you to define a custom error handling zone. It wraps Dart's native + /// [runZonedGuarded] function with Sentry-specific functionality. + /// + /// This function automatically records calls to `print()` as Breadcrumbs and + /// can be configured using [SentryOptions.enablePrintBreadcrumbs]. + /// + /// ```dart + /// Sentry.runZonedGuarded(() { + /// WidgetsBinding.ensureInitialized(); + /// + /// // Errors before init will not be handled by Sentry + /// + /// SentryFlutter.init( + /// (options) { + /// ... + /// }, + /// appRunner: () => runApp(MyApp()), + /// ); + /// } (error, stackTrace) { + /// // Automatically sends errors to Sentry, no need to do any + /// // captureException calls on your part. + /// // On top of that, you can do your own custom stuff in this callback. + /// }); + /// ``` + static dynamic runZonedGuarded( + R Function() body, + void Function(Object error, StackTrace stack)? onError, { + Map? zoneValues, + ZoneSpecification? zoneSpecification, + }) => + SentryRunZonedGuarded.sentryRunZonedGuarded( + _hub, + body, + onError, + zoneValues: zoneValues, + zoneSpecification: zoneSpecification, + ); + + static SentryLogger get logger => currentHub.options.logger; +} diff --git a/packages/dart/lib/src/sentry_attachment/io_sentry_attachment.dart b/packages/dart/lib/src/sentry_attachment/io_sentry_attachment.dart new file mode 100644 index 0000000000..ae07b51508 --- /dev/null +++ b/packages/dart/lib/src/sentry_attachment/io_sentry_attachment.dart @@ -0,0 +1,33 @@ +import 'dart:io'; + +import 'sentry_attachment.dart'; + +class IoSentryAttachment extends SentryAttachment { + /// Creates an attachment from a given path. + /// Only available on `dart:io` platforms. + /// Not available on web. + IoSentryAttachment.fromPath( + String path, { + String? filename, + String? attachmentType, + String? contentType, + }) : this.fromFile( + File(path), + attachmentType: attachmentType, + contentType: contentType, + filename: filename, + ); + + /// Creates an attachment from a given [File]. + /// Only available on `dart:io` platforms. + /// Not available on web. + IoSentryAttachment.fromFile( + File file, { + String? filename, + super.attachmentType, + super.contentType, + }) : super.fromLoader( + loader: () => file.readAsBytes(), + filename: filename ?? file.uri.pathSegments.last, + ); +} diff --git a/packages/dart/lib/src/sentry_attachment/sentry_attachment.dart b/packages/dart/lib/src/sentry_attachment/sentry_attachment.dart new file mode 100644 index 0000000000..5df3cde578 --- /dev/null +++ b/packages/dart/lib/src/sentry_attachment/sentry_attachment.dart @@ -0,0 +1,128 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import '../protocol/sentry_view_hierarchy.dart'; +import '../utils.dart'; + +// https://develop.sentry.dev/sdk/features/#attachments +// https://develop.sentry.dev/sdk/envelopes/#attachment + +typedef ContentLoader = FutureOr Function(); + +/// Arbitrary content which gets attached to an event. +class SentryAttachment { + /// Standard attachment without special meaning. + static const String typeAttachmentDefault = 'event.attachment'; + + /// Minidump file that creates an error event and is symbolicated. + /// The file should start with the `MDMP` magic bytes. + static const String typeMinidump = 'event.minidump'; + + /// Apple crash report file that creates an error event and is symbolicated. + static const String typeAppleCrashReport = 'event.applecrashreport'; + + /// XML file containing UE4 crash meta data. + /// During event ingestion, event contexts and extra fields are extracted from + /// this file. + static const String typeUnrealContext = 'unreal.context'; + + /// Plain-text log file obtained from UE4 crashes. + /// During event ingestion, the last logs are extracted into event + /// breadcrumbs. + static const String typeUnrealLogs = 'unreal.logs'; + + static const String typeViewHierarchy = 'event.view_hierarchy'; + + SentryAttachment.fromLoader({ + required ContentLoader loader, + required this.filename, + String? attachmentType, + this.contentType, + bool? addToTransactions, + }) : _loader = loader, + attachmentType = attachmentType ?? typeAttachmentDefault, + addToTransactions = addToTransactions ?? false; + + /// Creates an [SentryAttachment] from a [Uint8List] + SentryAttachment.fromUint8List( + Uint8List bytes, + String fileName, { + String? contentType, + String? attachmentType, + bool? addToTransactions, + }) : this.fromLoader( + attachmentType: attachmentType, + loader: () => bytes, + filename: fileName, + contentType: contentType, + addToTransactions: addToTransactions, + ); + + /// Creates an [SentryAttachment] from a [List] + SentryAttachment.fromIntList( + List bytes, + String fileName, { + String? contentType, + String? attachmentType, + bool? addToTransactions, + }) : this.fromLoader( + attachmentType: attachmentType, + loader: () => Uint8List.fromList(bytes), + filename: fileName, + contentType: contentType, + addToTransactions: addToTransactions, + ); + + /// Creates an [SentryAttachment] from [ByteData] + SentryAttachment.fromByteData( + ByteData bytes, + String fileName, { + String? contentType, + String? attachmentType, + bool? addToTransactions, + }) : this.fromLoader( + attachmentType: attachmentType, + loader: () => bytes.buffer.asUint8List(), + filename: fileName, + contentType: contentType, + addToTransactions: addToTransactions, + ); + + SentryAttachment.fromScreenshotData(Uint8List bytes) + : this.fromUint8List( + bytes, + 'screenshot.png', + contentType: 'image/png', + attachmentType: SentryAttachment.typeAttachmentDefault, + ); + + SentryAttachment.fromViewHierarchy(SentryViewHierarchy sentryViewHierarchy) + : this.fromLoader( + loader: () => Uint8List.fromList( + utf8JsonEncoder.convert(sentryViewHierarchy.toJson())), + filename: 'view-hierarchy.json', + contentType: 'application/json', + attachmentType: SentryAttachment.typeViewHierarchy, + ); + + /// Attachment type. + /// Should be one of the static types declared in [SentryAttachment]. + final String attachmentType; + + /// Attachment content. + /// Is loaded while sending this attachment. + FutureOr get bytes => _loader(); + + final ContentLoader _loader; + + /// Attachment file name. + final String filename; + + /// Attachment content type. + /// Inferred by Sentry if it's not given. + final String? contentType; + + /// If true, attachment should be added to every transaction. + /// Defaults to false. + final bool addToTransactions; +} diff --git a/packages/dart/lib/src/sentry_baggage.dart b/packages/dart/lib/src/sentry_baggage.dart new file mode 100644 index 0000000000..7baecc5309 --- /dev/null +++ b/packages/dart/lib/src/sentry_baggage.dart @@ -0,0 +1,222 @@ +import 'package:meta/meta.dart'; + +import 'protocol.dart'; +import 'scope.dart'; +import 'sentry_options.dart'; + +class SentryBaggage { + static const String _sampleRateKeyName = 'sentry-sample_rate'; + static const String _sampleRandKeyName = 'sentry-sample_rand'; + + static const int _maxChars = 8192; + static const int _maxListMember = 64; + + SentryBaggage( + this._keyValues, { + this.log, + }); + + final Map _keyValues; + final SdkLogCallback? log; + + String toHeaderString() { + final buffer = StringBuffer(); + var listMemberCount = 0; + var separator = ''; + + for (final entry in _keyValues.entries) { + if (listMemberCount >= _maxListMember) { + log?.call( + SentryLevel.info, + 'Baggage key ${entry.key} dropped because of max list member.', + ); + break; + } + try { + final encodedKey = _urlEncode(entry.key); + final encodedValue = _urlEncode(entry.value); + final encodedKeyValue = '$separator$encodedKey=$encodedValue'; + + final totalLengthIfValueAdded = buffer.length + encodedKeyValue.length; + + if (totalLengthIfValueAdded >= _maxChars) { + log?.call( + SentryLevel.info, + 'Baggage key ${entry.key} dropped because of max baggage chars.', + ); + continue; + } + + listMemberCount++; + buffer.write(encodedKeyValue); + separator = ','; + } catch (exception, stackTrace) { + log?.call( + SentryLevel.error, + 'Failed to parse the baggage key ${entry.key}.', + exception: exception, + stackTrace: stackTrace, + ); + // TODO rethrow in options.automatedTestMode (currently not available here to check) + } + } + + return buffer.toString(); + } + + factory SentryBaggage.fromHeaderList( + List headerValues, { + SdkLogCallback? log, + }) { + final keyValues = {}; + + for (final headerValue in headerValues) { + final keyValuesToAdd = _extractKeyValuesFromBaggageString( + headerValue, + log: log, + ); + keyValues.addAll(keyValuesToAdd); + } + + return SentryBaggage(keyValues, log: log); + } + + factory SentryBaggage.fromHeader( + String headerValue, { + SdkLogCallback? log, + }) { + final keyValues = _extractKeyValuesFromBaggageString( + headerValue, + log: log, + ); + + return SentryBaggage(keyValues, log: log); + } + + @internal + void setValuesFromScope(Scope scope, SentryOptions options) { + final propagationContext = scope.propagationContext; + setTraceId(propagationContext.traceId.toString()); + setPublicKey(options.parsedDsn.publicKey); + if (options.release != null) { + setRelease(options.release!); + } + if (options.environment != null) { + setEnvironment(options.environment!); + } + if (scope.user?.id != null) { + setUserId(scope.user!.id!); + } + if (scope.replayId != null && scope.replayId != SentryId.empty()) { + setReplayId(scope.replayId.toString()); + } + } + + static Map _extractKeyValuesFromBaggageString( + String headerValue, { + SdkLogCallback? log, + }) { + final keyValues = {}; + + final keyValueStrings = headerValue.split(','); + + for (final keyValueString in keyValueStrings) { + // TODO: Note, value MAY contain any number of the equal sign (=) characters. + // Parsers MUST NOT assume that the equal sign is only used to separate key and value. + final keyAndValue = keyValueString.split('='); + if (keyAndValue.length == 2) { + try { + final key = _urlDecode(keyAndValue.first.trim()); + final value = _urlDecode(keyAndValue.last.trim()); + keyValues[key] = value; + } catch (exception, stackTrace) { + log?.call( + SentryLevel.error, + 'Failed to parse the baggage entry $keyAndValue.', + exception: exception, + stackTrace: stackTrace, + ); + } + } + } + + return keyValues; + } + + static String _urlDecode(String uri) { + return Uri.decodeComponent(uri); + } + + String _urlEncode(String uri) { + return Uri.encodeComponent(uri); + } + + String? get(String key) => _keyValues[key]; + + void set(String key, String value) { + _keyValues[key] = value; + } + + void setTraceId(String value) { + set('sentry-trace_id', value); + } + + void setPublicKey(String value) { + set('sentry-public_key', value); + } + + void setEnvironment(String value) { + set('sentry-environment', value); + } + + void setRelease(String value) { + set('sentry-release', value); + } + + void setUserId(String value) { + set('sentry-user_id', value); + } + + void setTransaction(String value) { + set('sentry-transaction', value); + } + + void setSampleRate(String value) { + set(_sampleRateKeyName, value); + } + + void setSampleRand(String value) { + set(_sampleRandKeyName, value); + } + + void setSampled(String value) { + set('sentry-sampled', value); + } + + double? getSampleRate() { + final sampleRate = get(_sampleRateKeyName); + if (sampleRate == null) { + return null; + } + + return double.tryParse(sampleRate); + } + + double? getSampleRand() { + final sampleRand = get(_sampleRandKeyName); + if (sampleRand == null) { + return null; + } + + return double.tryParse(sampleRand); + } + + void setReplayId(String value) => set('sentry-replay_id', value); + + SentryId? getReplayId() { + final replayId = get('sentry-replay_id'); + return replayId == null ? null : SentryId.fromId(replayId); + } + + Map get keyValues => Map.unmodifiable(_keyValues); +} diff --git a/packages/dart/lib/src/sentry_client.dart b/packages/dart/lib/src/sentry_client.dart new file mode 100644 index 0000000000..7b27aa63e3 --- /dev/null +++ b/packages/dart/lib/src/sentry_client.dart @@ -0,0 +1,688 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:meta/meta.dart'; + +import 'client_reports/client_report_recorder.dart'; +import 'client_reports/discard_reason.dart'; +import 'event_processor/run_event_processors.dart'; +import 'hint.dart'; +import 'sdk_lifecycle_hooks.dart'; +import 'protocol.dart'; +import 'protocol/sentry_feedback.dart'; +import 'scope.dart'; +import 'sentry_attachment/sentry_attachment.dart'; +import 'sentry_baggage.dart'; +import 'sentry_envelope.dart'; +import 'sentry_exception_factory.dart'; +import 'sentry_options.dart'; +import 'sentry_stack_trace_factory.dart'; +import 'sentry_trace_context_header.dart'; +import 'transport/client_report_transport.dart'; +import 'transport/data_category.dart'; +import 'transport/http_transport.dart'; +import 'transport/noop_transport.dart'; +import 'transport/rate_limiter.dart'; +import 'transport/spotlight_http_transport.dart'; +import 'type_check_hint.dart'; +import 'utils/isolate_utils.dart'; +import 'utils/regex_utils.dart'; +import 'utils/stacktrace_utils.dart'; +import 'sentry_log_batcher.dart'; +import 'version.dart'; + +/// Default value for [SentryUser.ipAddress]. It gets set when an event does not have +/// a user and IP address. Only applies if [SentryOptions.sendDefaultPii] is set +/// to true. +const _defaultIpAddress = '{{auto}}'; + +@visibleForTesting +String get defaultIpAddress => _defaultIpAddress; + +/// Logs crash reports and events to the Sentry.io service. +class SentryClient { + final SentryOptions _options; + final Random? _random; + + static final _emptySentryId = Future.value(SentryId.empty()); + + late final SdkLifecycleRegistry _lifecycleRegistry; + + /// Allows registration and dispatching of callbacks outside of SentryClient + @internal + SdkLifecycleRegistry get lifeCycleRegistry => _lifecycleRegistry; + + SentryExceptionFactory get _exceptionFactory => _options.exceptionFactory; + SentryStackTraceFactory get _stackTraceFactory => _options.stackTraceFactory; + + /// Instantiates a client using [SentryOptions] + factory SentryClient(SentryOptions options) { + if (options.sendClientReports) { + options.recorder = ClientReportRecorder(options.clock); + } + RateLimiter? rateLimiter; + if (options.transport is NoOpTransport) { + rateLimiter = RateLimiter(options); + options.transport = HttpTransport(options, rateLimiter); + } + // rateLimiter is null if FileSystemTransport is active since Native SDKs take care of rate limiting + options.transport = ClientReportTransport( + rateLimiter, + options, + options.transport, + ); + // TODO: Use spotlight integration directly through JS SDK, then we can remove isWeb check + final enableFlutterSpotlight = (options.spotlight.enabled && + (options.platform.isWeb || + options.platform.isLinux || + options.platform.isWindows)); + // Spotlight in the Flutter layer is only enabled for Web, Linux and Windows + // Other platforms use spotlight through their native SDKs + if (enableFlutterSpotlight) { + options.transport = SpotlightHttpTransport(options, options.transport); + } + if (options.enableLogs) { + options.logBatcher = SentryLogBatcher(options); + } + return SentryClient._(options); + } + + /// Instantiates a client using [SentryOptions] + SentryClient._(this._options) + : _random = _options.sampleRate == null ? null : Random() { + _lifecycleRegistry = SdkLifecycleRegistry(_options); + } + + /// Reports an [event] to Sentry.io. + Future captureEvent( + SentryEvent event, { + Scope? scope, + dynamic stackTrace, + Hint? hint, + }) async { + if (_isIgnoredError(event)) { + _options.log( + SentryLevel.debug, + 'Error was ignored as specified in the ignoredErrors options.', + ); + _options.recorder + .recordLostEvent(DiscardReason.ignored, _getCategory(event)); + return _emptySentryId; + } + + if (_options.containsIgnoredExceptionForType(event.throwable)) { + _options.log( + SentryLevel.debug, + 'Event was dropped as the exception ${event.throwable.runtimeType.toString()} is ignored.', + ); + _options.recorder + .recordLostEvent(DiscardReason.eventProcessor, _getCategory(event)); + return _emptySentryId; + } + + if (_sampleRate() && event.type != 'feedback') { + _options.recorder + .recordLostEvent(DiscardReason.sampleRate, _getCategory(event)); + _options.log( + SentryLevel.debug, + 'Event ${event.eventId.toString()} was dropped due to sampling decision.', + ); + return _emptySentryId; + } + + hint ??= Hint(); + + SentryEvent? preparedEvent = + _prepareEvent(event, hint, stackTrace: stackTrace); + + if (scope != null) { + preparedEvent = await scope.applyToEvent(preparedEvent, hint); + } else { + _options.log( + SentryLevel.debug, 'No scope to apply on event was provided'); + } + + // dropped by scope event processors + if (preparedEvent == null) { + return _emptySentryId; + } + + preparedEvent = await runEventProcessors( + preparedEvent, + hint, + _options.eventProcessors, + _options, + ); + + // dropped by event processors + if (preparedEvent == null) { + return _emptySentryId; + } + + preparedEvent = _createUserOrSetDefaultIpAddress(preparedEvent); + + preparedEvent = await _runBeforeSend( + preparedEvent, + hint, + ); + + // dropped by beforeSend + if (preparedEvent == null) { + return _emptySentryId; + } + + // Event is fully processed and ready to be sent + await _lifecycleRegistry + .dispatchCallback(OnBeforeSendEvent(preparedEvent, hint)); + + var attachments = List.from(scope?.attachments ?? []); + attachments.addAll(hint.attachments); + var screenshot = hint.screenshot; + if (screenshot != null) { + attachments.add(screenshot); + } + + var viewHierarchy = hint.viewHierarchy; + if (viewHierarchy != null && event.type != 'feedback') { + attachments.add(viewHierarchy); + } + + var traceContext = scope?.span?.traceContext(); + if (traceContext == null) { + if (scope != null) { + scope.propagationContext.baggage ??= + SentryBaggage({}, log: _options.log) + ..setValuesFromScope(scope, _options); + traceContext = SentryTraceContextHeader.fromBaggage( + scope.propagationContext.baggage!); + } + } else { + traceContext.replayId = scope?.replayId; + } + + final envelope = SentryEnvelope.fromEvent( + preparedEvent, + _options.sdk, + dsn: _options.dsn, + traceContext: traceContext, + attachments: attachments.isNotEmpty ? attachments : null, + ); + + final id = await captureEnvelope(envelope); + return id ?? SentryId.empty(); + } + + bool _isIgnoredError(SentryEvent event) { + if (event.message == null || _options.ignoreErrors.isEmpty) { + return false; + } + + var message = event.message!.formatted; + return isMatchingRegexPattern(message, _options.ignoreErrors); + } + + SentryEvent _prepareEvent(SentryEvent event, Hint hint, + {dynamic stackTrace}) { + event + ..serverName = event.serverName ?? _options.serverName + ..dist = event.dist ?? _options.dist + ..environment = event.environment ?? _options.environment + ..release = event.release ?? _options.release + ..sdk = event.sdk ?? _options.sdk + ..platform = event.platform ?? sdkPlatform(_options.platform.isWeb); + + if (event is SentryTransaction) { + return event; + } + + if (event.type == 'feedback') { + return event; + } + + if (event.exceptions?.isNotEmpty ?? false) { + return event; + } + + final isolateName = getIsolateName(); + // Isolates have no id, so the hashCode of the name will be used as id + final isolateId = isolateName?.hashCode; + + if (event.throwableMechanism != null) { + final extractedExceptionCauses = _exceptionFactory.extractor + .flatten(event.throwableMechanism, stackTrace); + + SentryException? rootException; + SentryException? currentException; + final sentryThreads = []; + + for (final extractedExceptionCause in extractedExceptionCauses) { + var sentryException = _exceptionFactory.getSentryException( + extractedExceptionCause.exception, + stackTrace: extractedExceptionCause.stackTrace, + removeSentryFrames: hint.get(TypeCheckHint.currentStackTrace), + ); + if (extractedExceptionCause.source != null) { + var mechanism = + sentryException.mechanism ?? Mechanism(type: "generic"); + + mechanism.source = extractedExceptionCause.source; + sentryException.mechanism = mechanism; + } + + SentryThread? sentryThread; + + if (!_options.platform.isWeb && + isolateName != null && + _options.attachThreads) { + sentryException.threadId = isolateId; + sentryThread = SentryThread( + id: isolateId, + name: isolateName, + crashed: true, + current: true, + ); + } + + rootException ??= sentryException; + currentException?.addException(sentryException); + currentException = sentryException; + + if (sentryThread != null) { + sentryThreads.add(sentryThread); + } + } + + final exceptions = [...?event.exceptions]; + if (rootException != null) { + exceptions.add(rootException); + } + return event + ..exceptions = exceptions + ..threads = [ + ...?event.threads, + ...sentryThreads, + ]; + } + + // The stacktrace is not part of an exception, + // therefore add it to the threads. + // https://develop.sentry.dev/sdk/event-payloads/stacktrace/ + if (stackTrace != null || _options.attachStacktrace) { + if (stackTrace == null || stackTrace == StackTrace.empty) { + stackTrace = getCurrentStackTrace(); + hint.addAll({TypeCheckHint.currentStackTrace: true}); + } + final sentryStackTrace = _stackTraceFactory.parse( + stackTrace, + removeSentryFrames: hint.get(TypeCheckHint.currentStackTrace), + ); + if (sentryStackTrace.frames.isNotEmpty) { + event.threads = [ + ...?event.threads, + SentryThread( + name: isolateName, + id: isolateId, + crashed: false, + current: true, + stacktrace: sentryStackTrace, + ), + ]; + } + } + + return event; + } + + SentryEvent _createUserOrSetDefaultIpAddress(SentryEvent event) { + var user = event.user; + final effectiveIpAddress = + user?.ipAddress ?? (_options.sendDefaultPii ? _defaultIpAddress : null); + + if (effectiveIpAddress != null) { + user ??= SentryUser(ipAddress: effectiveIpAddress); + user.ipAddress = effectiveIpAddress; + event.user = user; + } + + return event; + } + + /// Reports the [throwable] and optionally its [stackTrace] to Sentry.io. + Future captureException( + dynamic throwable, { + dynamic stackTrace, + Scope? scope, + Hint? hint, + }) { + final event = SentryEvent( + throwable: throwable, + timestamp: _options.clock(), + ); + + return captureEvent( + event, + stackTrace: stackTrace, + scope: scope, + hint: hint, + ); + } + + /// Reports the [template] + Future captureMessage( + String formatted, { + SentryLevel? level, + String? template, + List? params, + Scope? scope, + Hint? hint, + }) { + final event = SentryEvent( + message: SentryMessage(formatted, template: template, params: params), + level: level ?? SentryLevel.info, + timestamp: _options.clock(), + ); + + return captureEvent(event, scope: scope, hint: hint); + } + + @internal + Future captureTransaction( + SentryTransaction transaction, { + Scope? scope, + SentryTraceContextHeader? traceContext, + Hint? hint, + }) async { + hint ??= Hint(); + + SentryTransaction? preparedTransaction = + _prepareEvent(transaction, hint) as SentryTransaction; + + if (scope != null) { + preparedTransaction = await scope.applyToEvent(preparedTransaction, hint) + as SentryTransaction?; + } else { + _options.log( + SentryLevel.debug, 'No scope to apply on transaction was provided'); + } + + // dropped by scope event processors + if (preparedTransaction == null) { + return _emptySentryId; + } + + preparedTransaction = await runEventProcessors( + preparedTransaction, + hint, + _options.eventProcessors, + _options, + ) as SentryTransaction?; + + // dropped by event processors + if (preparedTransaction == null) { + return _emptySentryId; + } + + if (_isIgnoredTransaction(preparedTransaction)) { + _options.log( + SentryLevel.debug, + 'Transaction was ignored as specified in the ignoredTransactions options.', + ); + + _options.recorder.recordLostEvent( + DiscardReason.ignored, _getCategory(preparedTransaction)); + return _emptySentryId; + } + + preparedTransaction = _createUserOrSetDefaultIpAddress(preparedTransaction) + as SentryTransaction; + + preparedTransaction = + await _runBeforeSend(preparedTransaction, hint) as SentryTransaction?; + + // dropped by beforeSendTransaction + if (preparedTransaction == null) { + return _emptySentryId; + } + + final attachments = scope?.attachments + .where((element) => element.addToTransactions) + .toList(); + final envelope = SentryEnvelope.fromTransaction( + preparedTransaction, + _options.sdk, + dsn: _options.dsn, + traceContext: traceContext, + attachments: attachments, + ); + + final profileInfo = preparedTransaction.tracer.profileInfo; + if (profileInfo != null) { + envelope.items.add(profileInfo.asEnvelopeItem()); + } + final id = await captureEnvelope(envelope); + + return id ?? SentryId.empty(); + } + + bool _isIgnoredTransaction(SentryTransaction transaction) { + if (_options.ignoreTransactions.isEmpty) { + return false; + } + + var name = transaction.tracer.name; + return isMatchingRegexPattern(name, _options.ignoreTransactions); + } + + /// Reports the [envelope] to Sentry.io. + Future captureEnvelope(SentryEnvelope envelope) { + return _options.transport.send(envelope); + } + + /// Reports the [feedback] to Sentry.io. + Future captureFeedback( + SentryFeedback feedback, { + Scope? scope, + Hint? hint, + }) { + // Cap feedback messages to max 4096 characters + if (feedback.message.length > 4096) { + feedback.message = feedback.message.substring(0, 4096); + } + final feedbackEvent = SentryEvent( + type: 'feedback', + contexts: Contexts(feedback: feedback), + level: SentryLevel.info, + ); + + return captureEvent( + feedbackEvent, + scope: scope, + hint: hint, + ); + } + + @internal + FutureOr captureLog( + SentryLog log, { + Scope? scope, + }) async { + if (!_options.enableLogs) { + return; + } + + log.attributes['sentry.sdk.name'] = SentryLogAttribute.string( + _options.sdk.name, + ); + log.attributes['sentry.sdk.version'] = SentryLogAttribute.string( + _options.sdk.version, + ); + final environment = _options.environment; + if (environment != null) { + log.attributes['sentry.environment'] = SentryLogAttribute.string( + environment, + ); + } + final release = _options.release; + if (release != null) { + log.attributes['sentry.release'] = SentryLogAttribute.string( + release, + ); + } + + final propagationContext = scope?.propagationContext; + if (propagationContext != null) { + log.traceId = propagationContext.traceId; + } + final span = scope?.span; + if (span != null) { + log.attributes['sentry.trace.parent_span_id'] = SentryLogAttribute.string( + span.context.spanId.toString(), + ); + } + + final user = scope?.user; + final id = user?.id; + final email = user?.email; + final name = user?.name; + if (id != null) { + log.attributes['user.id'] = SentryLogAttribute.string(id); + } + if (name != null) { + log.attributes['user.name'] = SentryLogAttribute.string(name); + } + if (email != null) { + log.attributes['user.email'] = SentryLogAttribute.string(email); + } + + final beforeSendLog = _options.beforeSendLog; + SentryLog? processedLog = log; + if (beforeSendLog != null) { + try { + final callbackResult = beforeSendLog(log); + + if (callbackResult is Future) { + processedLog = await callbackResult; + } else { + processedLog = callbackResult; + } + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'The beforeSendLog callback threw an exception', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } + } + + if (processedLog != null) { + await _lifecycleRegistry + .dispatchCallback(OnBeforeCaptureLog(processedLog)); + _options.logBatcher.addLog(processedLog); + } else { + _options.recorder.recordLostEvent( + DiscardReason.beforeSend, + DataCategory.logItem, + ); + } + } + + void close() { + _options.httpClient.close(); + } + + Future _runBeforeSend( + SentryEvent event, + Hint hint, + ) async { + SentryEvent? processedEvent = event; + final spanCountBeforeCallback = + event is SentryTransaction ? event.spans.length : 0; + + final beforeSend = _options.beforeSend; + final beforeSendTransaction = _options.beforeSendTransaction; + final beforeSendFeedback = _options.beforeSendFeedback; + String beforeSendName = 'beforeSend'; + + try { + if (event is SentryTransaction && beforeSendTransaction != null) { + beforeSendName = 'beforeSendTransaction'; + final callbackResult = beforeSendTransaction(event, hint); + if (callbackResult is Future) { + processedEvent = await callbackResult; + } else { + processedEvent = callbackResult; + } + } else if (event.type == 'feedback' && beforeSendFeedback != null) { + final callbackResult = beforeSendFeedback(event, hint); + if (callbackResult is Future) { + processedEvent = await callbackResult; + } else { + processedEvent = callbackResult; + } + } else if (beforeSend != null) { + final callbackResult = beforeSend(event, hint); + if (callbackResult is Future) { + processedEvent = await callbackResult; + } else { + processedEvent = callbackResult; + } + } + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'The $beforeSendName callback threw an exception', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } + + final discardReason = DiscardReason.beforeSend; + if (processedEvent == null) { + _options.recorder.recordLostEvent(discardReason, _getCategory(event)); + if (event is SentryTransaction) { + // We dropped the whole transaction, the dropped count includes all child spans + 1 root span + _options.recorder.recordLostEvent(discardReason, DataCategory.span, + count: spanCountBeforeCallback + 1); + } + _options.log( + SentryLevel.debug, + '${event.runtimeType} was dropped by $beforeSendName callback', + ); + } else if (event is SentryTransaction && + processedEvent is SentryTransaction) { + // If beforeSend removed only some spans we still report them as dropped + final spanCountAfterCallback = processedEvent.spans.length; + final droppedSpanCount = spanCountBeforeCallback - spanCountAfterCallback; + if (droppedSpanCount > 0) { + _options.recorder.recordLostEvent(discardReason, DataCategory.span, + count: droppedSpanCount); + } + } + + return processedEvent; + } + + bool _sampleRate() { + if (_options.sampleRate != null && _random != null) { + return (_options.sampleRate! < _random.nextDouble()); + } + return false; + } + + DataCategory _getCategory(SentryEvent event) { + if (event is SentryTransaction) { + return DataCategory.transaction; + } else if (event.type == 'feedback') { + return DataCategory.feedback; + } else { + return DataCategory.error; + } + } +} diff --git a/packages/dart/lib/src/sentry_envelope.dart b/packages/dart/lib/src/sentry_envelope.dart new file mode 100644 index 0000000000..83cccb14b7 --- /dev/null +++ b/packages/dart/lib/src/sentry_envelope.dart @@ -0,0 +1,137 @@ +import 'dart:convert'; + +import 'client_reports/client_report.dart'; +import 'protocol.dart'; +import 'sentry_attachment/sentry_attachment.dart'; +import 'sentry_envelope_header.dart'; +import 'sentry_envelope_item.dart'; +import 'sentry_item_type.dart'; +import 'sentry_options.dart'; +import 'sentry_trace_context_header.dart'; +import 'utils.dart'; + +/// Class representation of `Envelope` file. +class SentryEnvelope { + SentryEnvelope(this.header, this.items, + {this.containsUnhandledException = false}); + + /// Header describing envelope content. + final SentryEnvelopeHeader header; + + /// All items contained in the envelope. + final List items; + + /// Whether the envelope contains an unhandled exception. + /// This is used to determine if the native SDK should start a new session. + final bool containsUnhandledException; + + /// Create a [SentryEnvelope] containing one [SentryEnvelopeItem] which holds the [SentryEvent] data. + factory SentryEnvelope.fromEvent( + SentryEvent event, + SdkVersion sdkVersion, { + String? dsn, + SentryTraceContextHeader? traceContext, + List? attachments, + }) { + bool containsUnhandledException = false; + + if (event.exceptions != null && event.exceptions!.isNotEmpty) { + // Check all exceptions for any unhandled ones + containsUnhandledException = event.exceptions!.any((exception) { + return exception.mechanism?.handled == false; + }); + } + + return SentryEnvelope( + SentryEnvelopeHeader( + event.eventId, + sdkVersion, + dsn: dsn, + traceContext: traceContext, + ), + [ + SentryEnvelopeItem.fromEvent(event), + if (attachments != null) + ...attachments.map((e) => SentryEnvelopeItem.fromAttachment(e)) + ], + containsUnhandledException: containsUnhandledException, + ); + } + + /// Create a [SentryEnvelope] containing one [SentryEnvelopeItem] which holds the [SentryTransaction] data. + factory SentryEnvelope.fromTransaction( + SentryTransaction transaction, + SdkVersion sdkVersion, { + String? dsn, + SentryTraceContextHeader? traceContext, + List? attachments, + }) { + return SentryEnvelope( + SentryEnvelopeHeader( + transaction.eventId, + sdkVersion, + dsn: dsn, + traceContext: traceContext, + ), + [ + SentryEnvelopeItem.fromTransaction(transaction), + if (attachments != null) + ...attachments.map((e) => SentryEnvelopeItem.fromAttachment(e)) + ], + ); + } + + factory SentryEnvelope.fromLogs( + List items, + SdkVersion sdkVersion, + ) { + return SentryEnvelope( + SentryEnvelopeHeader( + null, + sdkVersion, + ), + [ + SentryEnvelopeItem.fromLogs(items), + ], + ); + } + + /// Stream binary data representation of `Envelope` file encoded. + Stream> envelopeStream(SentryOptions options) async* { + yield utf8JsonEncoder.convert(header.toJson()); + + final newLineData = utf8.encode('\n'); + for (final item in items) { + try { + final dataFuture = item.dataFactory(); + final data = dataFuture is Future ? await dataFuture : dataFuture; + + // Only attachments should be filtered according to + // SentryOptions.maxAttachmentSize + if (item.header.type == SentryItemType.attachment && + data.length > options.maxAttachmentSize) { + continue; + } + + yield newLineData; + yield utf8JsonEncoder.convert(await item.header.toJson(data.length)); + yield newLineData; + yield data; + } catch (_) { + if (options.automatedTestMode) { + rethrow; + } + // Skip throwing envelope item data closure. + continue; + } + } + } + + /// Add an envelope item containing client report data. + void addClientReport(ClientReport? clientReport) { + if (clientReport != null) { + final envelopeItem = SentryEnvelopeItem.fromClientReport(clientReport); + items.add(envelopeItem); + } + } +} diff --git a/packages/dart/lib/src/sentry_envelope_header.dart b/packages/dart/lib/src/sentry_envelope_header.dart new file mode 100644 index 0000000000..b7398985cc --- /dev/null +++ b/packages/dart/lib/src/sentry_envelope_header.dart @@ -0,0 +1,64 @@ +import 'protocol/sentry_id.dart'; +import 'protocol/sdk_version.dart'; +import 'sentry_trace_context_header.dart'; +import 'utils.dart'; + +/// Header containing `SentryId` and `SdkVersion`. +class SentryEnvelopeHeader { + SentryEnvelopeHeader( + this.eventId, + this.sdkVersion, { + this.dsn, + this.traceContext, + this.sentAt, + }); + SentryEnvelopeHeader.newEventId() + : eventId = SentryId.newId(), + sdkVersion = null, + dsn = null, + traceContext = null, + sentAt = null; + + /// The identifier of encoded `SentryEvent`. + final SentryId? eventId; + + /// The `SdkVersion` with which the envelope was send. + final SdkVersion? sdkVersion; + + final SentryTraceContextHeader? traceContext; + + /// The `DSN` of the Sentry project. + final String? dsn; + + DateTime? sentAt; + + /// Header encoded as JSON + Map toJson() { + final json = {}; + final tempEventId = eventId; + + if (tempEventId != null) { + json['event_id'] = tempEventId.toString(); + } + + final tempSdkVersion = sdkVersion; + if (tempSdkVersion != null) { + json['sdk'] = tempSdkVersion.toJson(); + } + + final tempTraceContext = traceContext; + if (tempTraceContext != null) { + json['trace'] = tempTraceContext.toJson(); + } + + if (dsn != null) { + json['dsn'] = dsn; + } + + if (sentAt != null) { + json['sent_at'] = formatDateAsIso8601WithMillisPrecision(sentAt!); + } + + return json; + } +} diff --git a/packages/dart/lib/src/sentry_envelope_item.dart b/packages/dart/lib/src/sentry_envelope_item.dart new file mode 100644 index 0000000000..06261380db --- /dev/null +++ b/packages/dart/lib/src/sentry_envelope_item.dart @@ -0,0 +1,86 @@ +import 'dart:async'; + +import 'client_reports/client_report.dart'; +import 'protocol.dart'; +import 'sentry_attachment/sentry_attachment.dart'; +import 'sentry_envelope_item_header.dart'; +import 'sentry_item_type.dart'; +import 'utils.dart'; + +/// Item holding header information and JSON encoded data. +class SentryEnvelopeItem { + /// The original, non-encoded object, used when direct access to the source data is needed. + Object? originalObject; + + SentryEnvelopeItem(this.header, this.dataFactory, {this.originalObject}); + + /// Creates a [SentryEnvelopeItem] which sends [SentryTransaction]. + factory SentryEnvelopeItem.fromTransaction(SentryTransaction transaction) { + final header = SentryEnvelopeItemHeader( + SentryItemType.transaction, + contentType: 'application/json', + ); + return SentryEnvelopeItem( + header, () => utf8JsonEncoder.convert(transaction.toJson()), + originalObject: transaction); + } + + factory SentryEnvelopeItem.fromAttachment(SentryAttachment attachment) { + final header = SentryEnvelopeItemHeader( + SentryItemType.attachment, + contentType: attachment.contentType, + fileName: attachment.filename, + attachmentType: attachment.attachmentType, + ); + return SentryEnvelopeItem( + header, + () => attachment.bytes, + originalObject: attachment, + ); + } + + /// Create a [SentryEnvelopeItem] which holds the [SentryEvent] data. + factory SentryEnvelopeItem.fromEvent(SentryEvent event) { + return SentryEnvelopeItem( + SentryEnvelopeItemHeader( + event.type == 'feedback' ? 'feedback' : SentryItemType.event, + contentType: 'application/json', + ), + () => utf8JsonEncoder.convert(event.toJson()), + originalObject: event, + ); + } + + /// Create a [SentryEnvelopeItem] which holds the [ClientReport] data. + factory SentryEnvelopeItem.fromClientReport(ClientReport clientReport) { + return SentryEnvelopeItem( + SentryEnvelopeItemHeader( + SentryItemType.clientReport, + contentType: 'application/json', + ), + () => utf8JsonEncoder.convert(clientReport.toJson()), + originalObject: clientReport, + ); + } + + factory SentryEnvelopeItem.fromLogs(List items) { + final payload = { + 'items': items.map((e) => e.toJson()).toList(), + }; + return SentryEnvelopeItem( + SentryEnvelopeItemHeader( + SentryItemType.log, + itemCount: items.length, + contentType: 'application/vnd.sentry.items.log+json', + ), + () => utf8JsonEncoder.convert(payload), + originalObject: payload, + ); + } + + /// Header with info about type and length of data in bytes. + final SentryEnvelopeItemHeader header; + + /// Create binary data representation of item data. + final FutureOr> Function() dataFactory; +} diff --git a/packages/dart/lib/src/sentry_envelope_item_header.dart b/packages/dart/lib/src/sentry_envelope_item_header.dart new file mode 100644 index 0000000000..31ff3d8602 --- /dev/null +++ b/packages/dart/lib/src/sentry_envelope_item_header.dart @@ -0,0 +1,33 @@ +/// Header with item info about type and length of data in bytes. +class SentryEnvelopeItemHeader { + SentryEnvelopeItemHeader( + this.type, { + this.itemCount, + this.contentType, + this.fileName, + this.attachmentType, + }); + + /// Type of encoded data. + final String type; + + final int? itemCount; + + final String? contentType; + + final String? fileName; + + final String? attachmentType; + + /// Item header encoded as JSON + Future> toJson(int length) async { + return { + if (itemCount != null) 'item_count': itemCount, + if (contentType != null) 'content_type': contentType, + if (fileName != null) 'filename': fileName, + if (attachmentType != null) 'attachment_type': attachmentType, + 'type': type, + 'length': length, + }; + } +} diff --git a/packages/dart/lib/src/sentry_event_like.dart b/packages/dart/lib/src/sentry_event_like.dart new file mode 100644 index 0000000000..4117e24261 --- /dev/null +++ b/packages/dart/lib/src/sentry_event_like.dart @@ -0,0 +1,7 @@ +import 'protocol.dart'; + +/// A [SentryEventLike] mixin that is extended by [SentryEvent] and [SentryTransaction] +mixin SentryEventLike { + @Deprecated('Assign values directly to the instance.') + T copyWith(); +} diff --git a/packages/dart/lib/src/sentry_exception_factory.dart b/packages/dart/lib/src/sentry_exception_factory.dart new file mode 100644 index 0000000000..15d474bf4f --- /dev/null +++ b/packages/dart/lib/src/sentry_exception_factory.dart @@ -0,0 +1,87 @@ +import 'protocol.dart'; +import 'recursive_exception_cause_extractor.dart'; +import 'sentry_options.dart'; +import 'sentry_stack_trace_factory.dart'; +import 'throwable_mechanism.dart'; +import 'utils/stacktrace_utils.dart'; + +/// class to convert Dart Error and exception to SentryException +class SentryExceptionFactory { + final SentryOptions _options; + + SentryStackTraceFactory get _stacktraceFactory => _options.stackTraceFactory; + + late final extractor = RecursiveExceptionCauseExtractor(_options); + + SentryExceptionFactory(this._options); + + SentryException getSentryException( + dynamic exception, { + dynamic stackTrace, + bool? removeSentryFrames, + }) { + var throwable = exception; + Mechanism? mechanism; + bool? snapshot; + if (exception is ThrowableMechanism) { + throwable = exception.throwable; + mechanism = exception.mechanism; + snapshot = exception.snapshot; + } + + if (throwable is Error) { + stackTrace ??= throwable.stackTrace; + } + stackTrace ??= _options + .exceptionStackTraceExtractor(throwable.runtimeType) + ?.stackTrace(throwable); + + // throwable.stackTrace is null if its an exception that was never thrown + // hence we check again if stackTrace is null and if not, read the current stack trace + // but only if attachStacktrace is enabled + + if (_options.attachStacktrace) { + if (stackTrace == null || stackTrace == StackTrace.empty) { + snapshot = true; + stackTrace = getCurrentStackTrace(); + removeSentryFrames = true; + } + } + + SentryStackTrace? sentryStackTrace; + if (stackTrace != null) { + sentryStackTrace = _stacktraceFactory.parse(stackTrace, + removeSentryFrames: removeSentryFrames); + sentryStackTrace.snapshot = snapshot; + if (sentryStackTrace.frames.isEmpty) { + sentryStackTrace = null; + } + } + + final throwableString = throwable.toString(); + final stackTraceString = stackTrace.toString(); + final value = throwableString.replaceAll(stackTraceString, '').trim(); + + String errorTypeName = throwable.runtimeType.toString(); + + if (_options.enableExceptionTypeIdentification) { + for (final errorTypeIdentifier in _options.exceptionTypeIdentifiers) { + final identifiedErrorType = errorTypeIdentifier.identifyType(throwable); + if (identifiedErrorType != null) { + errorTypeName = identifiedErrorType; + break; + } + } + } + + // if --obfuscate feature is enabled, 'type' won't be human readable. + // https://flutter.dev/docs/deployment/obfuscate#caveat + return SentryException( + type: errorTypeName, + value: value.isNotEmpty ? value : null, + mechanism: mechanism, + stackTrace: sentryStackTrace, + throwable: throwable, + ); + } +} diff --git a/packages/dart/lib/src/sentry_isolate.dart b/packages/dart/lib/src/sentry_isolate.dart new file mode 100644 index 0000000000..89b3e38ed2 --- /dev/null +++ b/packages/dart/lib/src/sentry_isolate.dart @@ -0,0 +1,93 @@ +import 'dart:isolate'; +import 'package:meta/meta.dart'; + +import 'throwable_mechanism.dart'; +import 'protocol.dart'; +import 'hub.dart'; +import 'hub_adapter.dart'; + +/// Conveniently spawn an isolate with an attached sentry error listener. +class SentryIsolate { + /// Calls [Isolate.spawn] with an error listener from the Sentry SDK. + /// + /// Providing your own `onError` will not add the listener from Sentry SDK. + static Future spawn( + void Function(T message) entryPoint, T message, + {bool paused = false, + bool errorsAreFatal = true, + SendPort? onExit, + SendPort? onError, + String? debugName, + @internal Hub? hub}) async { + return Isolate.spawn( + entryPoint, + message, + paused: paused, + errorsAreFatal: errorsAreFatal, + onExit: onExit, + onError: onError ?? createPort(hub ?? HubAdapter()).sendPort, + debugName: debugName, + ); + } + + @internal + static RawReceivePort createPort(Hub hub) { + return RawReceivePort( + (dynamic error) async { + await handleIsolateError(hub, error); + }, + ); + } + + @visibleForTesting + + /// Parse and raise an event out of the Isolate error. + static Future handleIsolateError( + Hub hub, + dynamic error, + ) async { + hub.options.log(SentryLevel.debug, 'Capture from IsolateError $error'); + + // https://api.dartlang.org/stable/2.7.0/dart-isolate/Isolate/addErrorListener.html + // error is a list of 2 elements + if (error is List && error.length == 2) { + /// The errors are sent back as two-element lists. + /// The first element is a `String` representation of the error, usually + /// created by calling `toString` on the error. + /// The second element is a `String` representation of an accompanying + /// stack trace, or `null` if no stack trace was provided. + /// To convert this back to a [StackTrace] object, use [StackTrace.fromString]. + final String throwable = error.first; + final String? stackTrace = error.last; + + hub.options.log( + SentryLevel.error, + 'Uncaught isolate error', + logger: 'sentry.isolateError', + exception: throwable, + stackTrace: + stackTrace == null ? null : StackTrace.fromString(stackTrace), + ); + + // Isolate errors don't crash the app, but is not handled by the user. + final mechanism = Mechanism(type: 'isolateError', handled: false); + final throwableMechanism = ThrowableMechanism(mechanism, throwable); + + final event = SentryEvent( + throwable: throwableMechanism, + level: hub.options.markAutomaticallyCollectedErrorsAsFatal + ? SentryLevel.fatal + : SentryLevel.error, + timestamp: hub.options.clock(), + ); + + // marks the span status if none to `internal_error` in case there's an + // unhandled error + hub.configureScope( + (scope) => scope.span?.status ??= const SpanStatus.internalError(), + ); + + await hub.captureEvent(event, stackTrace: stackTrace); + } + } +} diff --git a/packages/dart/lib/src/sentry_isolate_extension.dart b/packages/dart/lib/src/sentry_isolate_extension.dart new file mode 100644 index 0000000000..845fd99ce0 --- /dev/null +++ b/packages/dart/lib/src/sentry_isolate_extension.dart @@ -0,0 +1,29 @@ +import 'dart:isolate'; + +import 'package:meta/meta.dart'; + +import 'sentry_isolate.dart'; +import 'hub.dart'; +import 'hub_adapter.dart'; + +/// Record isolate errors with the Sentry SDK. +extension SentryIsolateExtension on Isolate { + /// Calls [addErrorListener] with an error listener from the Sentry SDK. Store + /// the returned [RawReceivePort] if you want to remove the Sentry listener + /// again. + /// + /// Since isolates run concurrently, it's possible for it to exit before the + /// error listener is established. To avoid this, start the isolate paused, + /// add the listener and then resume the isolate. + RawReceivePort addSentryErrorListener({@internal Hub? hub}) { + final port = SentryIsolate.createPort(hub ?? HubAdapter()); + addErrorListener(port.sendPort); + return port; + } + + /// Pass the [receivePort] returned from [addSentryErrorListener] to remove + /// the sentry error listener. + void removeSentryErrorListener(RawReceivePort receivePort) { + removeErrorListener(receivePort.sendPort); + } +} diff --git a/packages/dart/lib/src/sentry_item_type.dart b/packages/dart/lib/src/sentry_item_type.dart new file mode 100644 index 0000000000..c712ad8793 --- /dev/null +++ b/packages/dart/lib/src/sentry_item_type.dart @@ -0,0 +1,10 @@ +class SentryItemType { + static const String event = 'event'; + static const String attachment = 'attachment'; + static const String transaction = 'transaction'; + static const String clientReport = 'client_report'; + static const String profile = 'profile'; + static const String statsd = 'statsd'; + static const String log = 'log'; + static const String unknown = '__unknown__'; +} diff --git a/packages/dart/lib/src/sentry_log_batcher.dart b/packages/dart/lib/src/sentry_log_batcher.dart new file mode 100644 index 0000000000..d5f023f979 --- /dev/null +++ b/packages/dart/lib/src/sentry_log_batcher.dart @@ -0,0 +1,52 @@ +import 'dart:async'; +import 'sentry_envelope.dart'; +import 'sentry_options.dart'; +import 'protocol/sentry_log.dart'; +import 'package:meta/meta.dart'; + +@internal +class SentryLogBatcher { + SentryLogBatcher(this._options, {Duration? flushTimeout, int? maxBufferSize}) + : _flushTimeout = flushTimeout ?? Duration(seconds: 5), + _maxBufferSize = maxBufferSize ?? 100; + + final SentryOptions _options; + final Duration _flushTimeout; + final int _maxBufferSize; + + final _logBuffer = []; + + Timer? _flushTimer; + + void addLog(SentryLog log) { + _logBuffer.add(log); + + _flushTimer?.cancel(); + + if (_logBuffer.length >= _maxBufferSize) { + return flush(); + } else { + _flushTimer = Timer(_flushTimeout, flush); + } + } + + void flush() { + _flushTimer?.cancel(); + _flushTimer = null; + + final logs = List.from(_logBuffer); + _logBuffer.clear(); + + if (logs.isEmpty) { + return; + } + + final envelope = SentryEnvelope.fromLogs( + logs, + _options.sdk, + ); + + // TODO: Make sure the Android SDK understands the log envelope type. + _options.transport.send(envelope); + } +} diff --git a/packages/dart/lib/src/sentry_logger.dart b/packages/dart/lib/src/sentry_logger.dart new file mode 100644 index 0000000000..930b78166e --- /dev/null +++ b/packages/dart/lib/src/sentry_logger.dart @@ -0,0 +1,75 @@ +import 'dart:async'; +import 'hub.dart'; +import 'hub_adapter.dart'; +import 'protocol/sentry_log.dart'; +import 'protocol/sentry_log_level.dart'; +import 'protocol/sentry_log_attribute.dart'; +import 'sentry_options.dart'; +import 'sentry_logger_formatter.dart'; + +class SentryLogger { + SentryLogger(this._clock, {Hub? hub}) : _hub = hub ?? HubAdapter(); + + final ClockProvider _clock; + final Hub _hub; + + late final fmt = SentryLoggerFormatter(this); + + FutureOr trace( + String body, { + Map? attributes, + }) { + return _captureLog(SentryLogLevel.trace, body, attributes: attributes); + } + + FutureOr debug( + String body, { + Map? attributes, + }) { + return _captureLog(SentryLogLevel.debug, body, attributes: attributes); + } + + FutureOr info( + String body, { + Map? attributes, + }) { + return _captureLog(SentryLogLevel.info, body, attributes: attributes); + } + + FutureOr warn( + String body, { + Map? attributes, + }) { + return _captureLog(SentryLogLevel.warn, body, attributes: attributes); + } + + FutureOr error( + String body, { + Map? attributes, + }) { + return _captureLog(SentryLogLevel.error, body, attributes: attributes); + } + + FutureOr fatal( + String body, { + Map? attributes, + }) { + return _captureLog(SentryLogLevel.fatal, body, attributes: attributes); + } + + // Helper + + FutureOr _captureLog( + SentryLogLevel level, + String body, { + Map? attributes, + }) { + final log = SentryLog( + timestamp: _clock(), + level: level, + body: body, + attributes: attributes ?? {}, + ); + return _hub.captureLog(log); + } +} diff --git a/packages/dart/lib/src/sentry_logger_formatter.dart b/packages/dart/lib/src/sentry_logger_formatter.dart new file mode 100644 index 0000000000..de5ebcca08 --- /dev/null +++ b/packages/dart/lib/src/sentry_logger_formatter.dart @@ -0,0 +1,147 @@ +import 'dart:async'; +import 'protocol/sentry_log_attribute.dart'; +import 'sentry_template_string.dart'; +import 'sentry_logger.dart'; + +class SentryLoggerFormatter { + SentryLoggerFormatter(this._logger); + + final SentryLogger _logger; + + FutureOr trace( + String templateBody, + List arguments, { + Map? attributes, + }) { + return _format( + templateBody, + arguments, + attributes, + (formattedBody, allAttributes) { + return _logger.trace(formattedBody, attributes: allAttributes); + }, + ); + } + + FutureOr debug( + String templateBody, + List arguments, { + Map? attributes, + }) { + return _format( + templateBody, + arguments, + attributes, + (formattedBody, allAttributes) { + return _logger.debug(formattedBody, attributes: allAttributes); + }, + ); + } + + FutureOr info( + String templateBody, + List arguments, { + Map? attributes, + }) { + return _format( + templateBody, + arguments, + attributes, + (formattedBody, allAttributes) { + return _logger.info(formattedBody, attributes: allAttributes); + }, + ); + } + + FutureOr warn( + String templateBody, + List arguments, { + Map? attributes, + }) { + return _format( + templateBody, + arguments, + attributes, + (formattedBody, allAttributes) { + return _logger.warn(formattedBody, attributes: allAttributes); + }, + ); + } + + FutureOr error( + String templateBody, + List arguments, { + Map? attributes, + }) { + return _format( + templateBody, + arguments, + attributes, + (formattedBody, allAttributes) { + return _logger.error(formattedBody, attributes: allAttributes); + }, + ); + } + + FutureOr fatal( + String templateBody, + List arguments, { + Map? attributes, + }) { + return _format( + templateBody, + arguments, + attributes, + (formattedBody, allAttributes) { + return _logger.fatal(formattedBody, attributes: allAttributes); + }, + ); + } + + // Helper + + FutureOr _format( + String templateBody, + List arguments, + Map? attributes, + FutureOr Function(String, Map) callback, + ) { + final templateString = SentryTemplateString(templateBody, arguments); + final formattedBody = templateString.format(); + final templateAttributes = _getAllAttributes(templateBody, arguments); + if (attributes != null) { + templateAttributes.addAll(attributes); + } + return callback(formattedBody, templateAttributes); + } + + Map _getAllAttributes( + String templateBody, + List args, + ) { + final templateAttributes = { + 'sentry.message.template': SentryLogAttribute.string(templateBody), + }; + for (var i = 0; i < args.length; i++) { + final argument = args[i]; + final key = 'sentry.message.parameter.$i'; + if (argument is String) { + templateAttributes[key] = SentryLogAttribute.string(argument); + } else if (argument is int) { + templateAttributes[key] = SentryLogAttribute.int(argument); + } else if (argument is bool) { + templateAttributes[key] = SentryLogAttribute.bool(argument); + } else if (argument is double) { + templateAttributes[key] = SentryLogAttribute.double(argument); + } else { + try { + templateAttributes[key] = + SentryLogAttribute.string(argument.toString()); + } catch (e) { + templateAttributes[key] = SentryLogAttribute.string(""); + } + } + } + return templateAttributes; + } +} diff --git a/packages/dart/lib/src/sentry_measurement.dart b/packages/dart/lib/src/sentry_measurement.dart new file mode 100644 index 0000000000..965bd56272 --- /dev/null +++ b/packages/dart/lib/src/sentry_measurement.dart @@ -0,0 +1,76 @@ +import 'sentry_measurement_unit.dart'; + +class SentryMeasurement { + SentryMeasurement( + this.name, + this.value, { + this.unit, + }); + + static const totalFramesName = 'frames_total'; + static const slowFramesName = 'frames_slow'; + static const frozenFramesName = 'frames_frozen'; + static const framesDelayName = 'frames_delay'; + + /// Amount of frames drawn during a transaction + SentryMeasurement.totalFrames(this.value) + : name = totalFramesName, + unit = SentryMeasurementUnit.none; + + /// Amount of slow frames drawn during a transaction. + /// A slow frame is any frame longer than 1s / refreshrate. + /// So for example any frame slower than 16ms for a refresh rate of 60hz. + SentryMeasurement.slowFrames(this.value) + : name = slowFramesName, + unit = SentryMeasurementUnit.none; + + /// Amount of frozen frames drawn during a transaction. + /// Typically defined as frames slower than 500ms. + SentryMeasurement.frozenFrames(this.value) + : name = frozenFramesName, + unit = SentryMeasurementUnit.none; + + /// Total duration of frames delayed. + SentryMeasurement.framesDelay(this.value) + : name = framesDelayName, + unit = SentryMeasurementUnit.none; + + /// Duration of the Cold App start in milliseconds + SentryMeasurement.coldAppStart(Duration duration) + : assert(!duration.isNegative), + name = 'app_start_cold', + value = duration.inMilliseconds, + unit = DurationSentryMeasurementUnit.milliSecond; + + /// Duration of the Warm App start in milliseconds + SentryMeasurement.warmAppStart(Duration duration) + : assert(!duration.isNegative), + name = 'app_start_warm', + value = duration.inMilliseconds, + unit = DurationSentryMeasurementUnit.milliSecond; + + /// Duration of the time to initial display in milliseconds + SentryMeasurement.timeToInitialDisplay(Duration duration) + : assert(!duration.isNegative), + name = 'time_to_initial_display', + value = duration.inMilliseconds, + unit = DurationSentryMeasurementUnit.milliSecond; + + /// Duration of the time to full display in milliseconds + SentryMeasurement.timeToFullDisplay(Duration duration) + : assert(!duration.isNegative), + name = 'time_to_full_display', + value = duration.inMilliseconds, + unit = DurationSentryMeasurementUnit.milliSecond; + + final String name; + final num value; + final SentryMeasurementUnit? unit; + + Map toJson() { + return { + 'value': value, + if (unit != null) 'unit': unit?.toStringValue(), + }; + } +} diff --git a/packages/dart/lib/src/sentry_measurement_unit.dart b/packages/dart/lib/src/sentry_measurement_unit.dart new file mode 100644 index 0000000000..96376a14f9 --- /dev/null +++ b/packages/dart/lib/src/sentry_measurement_unit.dart @@ -0,0 +1,122 @@ +/// The unit of measurement of a metric value. +/// Units augment metric values by giving them a magnitude and semantics. +/// Units and their precisions are uniquely represented by a string identifier. +abstract class SentryMeasurementUnit { + static final none = NoneSentryMeasurementUnit(); + String get name; +} + +extension SentryMeasurementUnitExtension on SentryMeasurementUnit { + String toStringValue() { + return name; + } +} + +enum DurationSentryMeasurementUnit implements SentryMeasurementUnit { + /// Nanosecond (`"nanosecond"`), 10^-9 seconds. + nanoSecond('nanosecond'), + + /// Microsecond (`"microsecond"`), 10^-6 seconds. + microSecond('microsecond'), + + /// Millisecond (`"millisecond"`), 10^-3 seconds. + milliSecond('millisecond'), + + /// Full second (`"second"`). + second('second'), + + /// Minute (`"minute"`), 60 seconds. + minute('minute'), + + /// Hour (`"hour"`), 3600 seconds. + hour('hour'), + + /// Day (`"day"`), 86,400 seconds. + day('day'), + + /// Week (`"week"`), 604,800 seconds. + week('week'); + + const DurationSentryMeasurementUnit(this.name); + + @override + final String name; +} + +enum InformationSentryMeasurementUnit implements SentryMeasurementUnit { + /// Bit (`"bit"`), corresponding to 1/8 of a byte. + bit("bit"), + + /// Byte (`"byte"`). + byte('byte'), + + /// Kilobyte (`"kilobyte"`), 10^3 bytes. + kiloByte('kilobyte'), + + /// Kibibyte (`"kibibyte"`), 2^10 bytes. + kibiByte('kibibyte'), + + /// Megabyte (`"megabyte"`), 10^6 bytes. + megaByte('megabyte'), + + /// Mebibyte (`"mebibyte"`), 2^20 bytes. + mebiByte('mebibyte'), + + /// Gigabyte (`"gigabyte"`), 10^9 bytes. + gigaByte('gigabyte'), + + /// Gibibyte (`"gibibyte"`), 2^30 bytes. + gibiByte('gibibyte'), + + /// Terabyte (`"terabyte"`), 10^12 bytes. + teraByte('terabyte'), + + /// Tebibyte (`"tebibyte"`), 2^40 bytes. + tebiByte('tebibyte'), + + /// Petabyte (`"petabyte"`), 10^15 bytes. + petaByte('petabyte'), + + /// Pebibyte (`"pebibyte"`), 2^50 bytes. + pebiByte('pebibyte'), + + /// Exabyte (`"exabyte"`), 10^18 bytes. + exaByte('exabyte'), + + /// Exbibyte (`"exbibyte"`), 2^60 bytes. + exbiByte('exbibyte'); + + const InformationSentryMeasurementUnit(this.name); + + @override + final String name; +} + +enum FractionSentryMeasurementUnit implements SentryMeasurementUnit { + /// Floating point fraction of `1`. + ratio('ratio'), + + /// Ratio expressed as a fraction of `100`. `100%` equals a ratio of `1.0`. + percent('percent'); + + const FractionSentryMeasurementUnit(this.name); + + @override + final String name; +} + +/// Custom units without builtin conversion. No formatting will be applied to +/// the measurement value in the Sentry product, and the value with the unit +/// will be shown as is. +class CustomSentryMeasurementUnit implements SentryMeasurementUnit { + CustomSentryMeasurementUnit(this.name); + + @override + final String name; +} + +/// Untyped value. +class NoneSentryMeasurementUnit implements SentryMeasurementUnit { + @override + String get name => 'none'; +} diff --git a/packages/dart/lib/src/sentry_options.dart b/packages/dart/lib/src/sentry_options.dart new file mode 100644 index 0000000000..7aba9721d8 --- /dev/null +++ b/packages/dart/lib/src/sentry_options.dart @@ -0,0 +1,682 @@ +import 'dart:async'; + +import 'package:http/http.dart'; +import 'package:meta/meta.dart'; + +import '../sentry.dart'; +import 'client_reports/client_report_recorder.dart'; +import 'client_reports/noop_client_report_recorder.dart'; +import 'diagnostic_log.dart'; +import 'environment/environment_variables.dart'; +import 'noop_client.dart'; +import 'platform/platform.dart'; +import 'sentry_exception_factory.dart'; +import 'sentry_stack_trace_factory.dart'; +import 'transport/noop_transport.dart'; +import 'version.dart'; +import 'sentry_log_batcher.dart'; +import 'noop_log_batcher.dart'; +import 'dart:developer' as developer; + +// TODO: shutdownTimeout, flushTimeoutMillis +// https://api.dart.dev/stable/2.10.2/dart-io/HttpClient/close.html doesn't have a timeout param, we'd need to implement manually + +/// Sentry SDK options +class SentryOptions { + /// Default Log level if not specified Default is WARNING + static final SentryLevel _defaultDiagnosticLevel = SentryLevel.warning; + + String? _dsn; + Dsn? _parsedDsn; + + /// The DSN tells the SDK where to send the events to. + /// If an empty string is used, the SDK will not send any events. + String? get dsn => _dsn; + + set dsn(String? value) { + if (_dsn != value) { + _dsn = value; + _parsedDsn = null; // Invalidate the cached parsed DSN + } + } + + /// Evaluates and parses the DSN. + /// May throw an exception if the DSN is invalid. + @internal + Dsn get parsedDsn { + _parsedDsn ??= _parseDsn(); + return _parsedDsn!; + } + + Dsn _parseDsn() { + if (_dsn == null || _dsn!.isEmpty) { + throw StateError('DSN is null or empty'); + } + return Dsn.parse(_dsn!); + } + + /// If [compressPayload] is `true` the outgoing HTTP payloads are compressed + /// using gzip. Otherwise, the payloads are sent in plain UTF8-encoded JSON + /// text. The compression is enabled by default. + bool compressPayload = true; + + /// If [httpClient] is provided, it is used instead of the default client to + /// make HTTP calls to Sentry.io. This is useful in tests. + /// If you don't need to send events, use [NoOpClient]. + Client httpClient = NoOpClient(); + + /// If [clock] is provided, it is used to get time instead of the system + /// clock. This is useful in tests. Should be an implementation of [ClockProvider]. + /// The ClockProvider is expected to return UTC time. + @internal + ClockProvider clock = getUtcDateTime; + + int _maxBreadcrumbs = 100; + + /// This variable controls the total amount of breadcrumbs that should be captured Default is 100 + int get maxBreadcrumbs => _maxBreadcrumbs; + + set maxBreadcrumbs(int maxBreadcrumbs) { + assert(maxBreadcrumbs >= 0); + _maxBreadcrumbs = maxBreadcrumbs; + } + + /// Initial value of 20 MiB according to + /// https://develop.sentry.dev/sdk/features/#max-attachment-size + int _maxAttachmentSize = 20 * 1024 * 1024; + + /// Maximum allowed file size of attachments, in bytes. + /// Attachments above this size will be discarded + /// + /// Remarks: Regardless of this setting, attachments are also limited to 20mb + /// (compressed) on Relay. + int get maxAttachmentSize => _maxAttachmentSize; + + set maxAttachmentSize(int maxAttachmentSize) { + assert(maxAttachmentSize > 0); + _maxAttachmentSize = maxAttachmentSize; + } + + /// Maximum number of spans that can be attached to single transaction. + int _maxSpans = 1000; + + /// Returns the maximum number of spans that can be attached to single transaction. + int get maxSpans => _maxSpans; + + /// Sets the maximum number of spans that can be attached to single transaction. + set maxSpans(int maxSpans) { + assert(maxSpans > 0); + _maxSpans = maxSpans; + } + + int _maxQueueSize = 30; + + /// Returns the max number of events Sentry will send when calling capture + /// methods in a tight loop. Default is 30. + int get maxQueueSize => _maxQueueSize; + + /// Sets how many unawaited events can be sent by Sentry. (e.g. capturing + /// events in a tight loop) at once. If you need to send more, please use the + /// await keyword. + set maxQueueSize(int count) { + assert(count > 0); + _maxQueueSize = count; + } + + /// Configures up to which size request bodies should be included in events. + /// This does not change whether an event is captured. + MaxRequestBodySize maxRequestBodySize = MaxRequestBodySize.never; + + SdkLogCallback _log = noOpLog; + + /// Log callback to log useful debugging information if debug is enabled + SdkLogCallback get log => _log; + + @internal + set log(SdkLogCallback value) { + diagnosticLog = DiagnosticLog(value, this); + _log = diagnosticLog!.log; + } + + @visibleForTesting + DiagnosticLog? diagnosticLog; + + final List _eventProcessors = []; + + /// Are callbacks that run for every event. They can either return a new event which in most cases + /// means just adding data OR return null in case the event will be dropped and not sent. + /// + /// Global Event processors are executed after the Scope's processors + List get eventProcessors => + List.unmodifiable(_eventProcessors); + + final List _integrations = []; + + /// Code that provides middlewares, bindings or hooks into certain frameworks or environments, + /// along with code that inserts those bindings and activates them. + List get integrations => List.unmodifiable(_integrations); + + /// Turns debug mode on or off. If debug is enabled SDK will attempt to print out useful debugging + /// information if something goes wrong. Default is enabled in debug mode, otherwise it is disabled. + bool get debug => _debug; + + set debug(bool newValue) { + _debug = newValue; + if (_debug == true && + (log == noOpLog || diagnosticLog?.logger == noOpLog)) { + log = debugLog; + } + if (_debug == false && + (log == debugLog || diagnosticLog?.logger == debugLog)) { + log = noOpLog; + } + } + + bool _debug = false; + + /// minimum LogLevel to be used if debug is enabled + SentryLevel diagnosticLevel = _defaultDiagnosticLevel; + + /// Sentry client name used for the HTTP authHeader and userAgent eg + /// sentry.{language}.{platform}/{version} eg sentry.java.android/2.0.0 would be a valid case + String get sentryClientName => '${sdk.name}/${sdk.version}'; + + /// This function is called with an SDK specific event object and can return a modified event + /// object or nothing to skip reporting the event + BeforeSendCallback? beforeSend; + + /// This function is called with an SDK specific transaction object and can return a modified + /// transaction object or nothing to skip reporting the transaction + BeforeSendTransactionCallback? beforeSendTransaction; + + /// This function is called with an SDK specific feedback event object and can return a modified + /// feedback event object or nothing to skip reporting the feedback event + BeforeSendCallback? beforeSendFeedback; + + /// This function is called with an SDK specific breadcrumb object before the breadcrumb is added + /// to the scope. When nothing is returned from the function, the breadcrumb is dropped + BeforeBreadcrumbCallback? beforeBreadcrumb; + + /// This function is called right before a metric is about to be emitted. + /// Can return true to emit the metric, or false to drop it. + BeforeMetricCallback? beforeMetricCallback; + + /// This function is called right before a log is about to be sent. + /// Can return a modified log or null to drop the log. + BeforeSendLogCallback? beforeSendLog; + + /// Sets the release. SDK will try to automatically configure a release out of the box + /// See [docs for further information](https://docs.sentry.io/platforms/flutter/configuration/releases/) + String? release; + + /// Sets the environment. This string is freeform and not set by default. A release can be + /// associated with more than one environment to separate them in the UI Think staging vs prod or + /// similar. + /// See [docs for further information](https://docs.sentry.io/platforms/flutter/configuration/environments/) + String? environment; + + /// Configures the sample rate as a percentage of events to be sent in the range of 0.0 to 1.0. if + /// 1.0 is set it means that 100% of events are sent. If set to 0.1 only 10% of events will be + /// sent. Events are picked randomly. Default is null (disabled) + double? sampleRate; + + /// The ignoreErrors tells the SDK which errors should be not sent to the sentry server. + /// If an null or an empty list is used, the SDK will send all transactions. + /// To use regex add the `^` and the `$` to the string. + List ignoreErrors = []; + + /// The ignoreTransactions tells the SDK which transactions should be not sent to the sentry server. + /// If null or an empty list is used, the SDK will send all transactions. + /// To use regex add the `^` and the `$` to the string. + List ignoreTransactions = []; + + final List _inAppExcludes = []; + + /// A list of string prefixes of packages names that do not belong to the app, but rather third-party + /// packages. Packages considered not to be part of the app will be hidden from stack traces by + /// default. + /// example : `['sentry']` will exclude exception from `package:sentry/sentry.dart` + List get inAppExcludes => List.unmodifiable(_inAppExcludes); + + final List _inAppIncludes = []; + + /// A list of string prefixes of packages names that belong to the app. This option takes precedence + /// over inAppExcludes. + /// example: `['sentry']` will include exception from `package:sentry/sentry.dart` + List get inAppIncludes => List.unmodifiable(_inAppIncludes); + + /// Configures whether stack trace frames are considered in app frames by default. + /// You can use this to essentially make [inAppIncludes] or [inAppExcludes] + /// an allow or deny list. + /// This value is only used if Sentry can not find the origin of the frame. + /// + /// - If [considerInAppFramesByDefault] is true you only need to maintain + /// [inAppExcludes]. + /// - If [considerInAppFramesByDefault] is false you only need to maintain + /// [inAppIncludes]. + bool considerInAppFramesByDefault = true; + + /// The transport is an internal construct of the client that abstracts away the event sending. + Transport transport = NoOpTransport(); + + /// Sets the distribution. Think about it together with release and environment + String? dist; + + /// The server name used in the Sentry messages. + String? serverName; + + /// Sdk object that contains the Sentry Client Name and its version + late SdkVersion sdk; + + /// When enabled, stack traces are automatically attached to all messages logged. + /// Stack traces are always attached to exceptions; + /// however, when this option is set, stack traces are also sent with messages. + /// This option, for instance, means that stack traces appear next to all log messages. + /// + /// This option is `true` by default. + /// + /// Grouping in Sentry is different for events with stack traces and without. + /// As a result, you will get new groups as you enable or disable this flag for certain events. + bool attachStacktrace = true; + + /// Enable this option if you want to record calls to `print()` as + /// breadcrumbs. + /// In a Flutter environment, this setting also toggles recording of `debugPrint` calls. + /// `debugPrint` calls are only recorded in release builds, though. + bool enablePrintBreadcrumbs = true; + + /// If [runtimeChecker] is provided, it is used get the environment. + /// This is useful in tests. Should be an implementation of [RuntimeChecker]. + RuntimeChecker runtimeChecker = RuntimeChecker(); + + /// Info on which platform the SDK runs. + Platform platform = Platform(); + + /// If [environmentVariables] is provided, it is used get the environment + /// variables. This is useful in tests. + EnvironmentVariables environmentVariables = EnvironmentVariables.instance(); + + /// When enabled, the current isolate will be attached to the event. + /// This only applies to Dart:io platforms and only the current isolate. + /// The Dart runtime doesn't provide information about other active isolates. + /// + /// When running on web, this option has no effect at all. + /// + /// When running in the Flutter context, this enables attaching of threads + /// for native events, if supported for the native platform. + /// Currently, this is only supported on Android. + bool attachThreads = false; + + /// Whether to send personal identifiable information along with events + bool sendDefaultPii = false; + + /// Configures whether to record exceptions for failed requests. + /// Examples for captures exceptions are: + /// - In an browser environment this can be requests which fail because of CORS. + /// - In an mobile or desktop application this can be requests which failed + /// because the connection was interrupted. + /// Use with [SentryHttpClient] or `sentry_dio` integration for this to work, + /// or iOS native where it sets the value to `enableCaptureFailedRequests`. + bool captureFailedRequests = true; + + /// Whether to records requests as breadcrumbs. This is on by default. + /// It only has an effect when the SentryHttpClient or dio integration is in + /// use, or iOS native where it sets the value to `enableNetworkBreadcrumbs`. + bool recordHttpBreadcrumbs = true; + + /// Whether [SentryEvent] deduplication is enabled. + /// Can be further configured with [maxDeduplicationItems]. + /// Shoud be set to true if + /// [SentryHttpClient] is used to capture failed requests. + bool enableDeduplication = true; + + int _maxDeduplicationItems = 5; + + /// Describes how many exceptions are kept to be checked for deduplication. + /// This should be a small positiv integer in order to keep deduplication + /// performant. + /// Is only in effect if [enableDeduplication] is set to true. + int get maxDeduplicationItems => _maxDeduplicationItems; + + set maxDeduplicationItems(int count) { + assert(count > 0); + _maxDeduplicationItems = count; + } + + double? _tracesSampleRate; + + /// Returns the traces sample rate Default is null (disabled) + double? get tracesSampleRate => _tracesSampleRate; + + set tracesSampleRate(double? tracesSampleRate) { + assert(tracesSampleRate == null || + (tracesSampleRate >= 0 && tracesSampleRate <= 1)); + _tracesSampleRate = tracesSampleRate; + } + + /// This function is called by [TracesSamplerCallback] to determine if transaction is sampled - meant + /// to be sent to Sentry. + TracesSamplerCallback? tracesSampler; + + double? _profilesSampleRate; + + @internal // Only exposed by SentryFlutterOptions at the moment. + double? get profilesSampleRate => _profilesSampleRate; + + @internal // Only exposed by SentryFlutterOptions at the moment. + set profilesSampleRate(double? value) { + assert(value == null || (value >= 0 && value <= 1)); + _profilesSampleRate = value; + } + + /// Send statistics to sentry when the client drops events. + bool sendClientReports = true; + + /// If enabled, [scopeObservers] will be called when mutating scope. + bool enableScopeSync = true; + + final List _scopeObservers = []; + + List get scopeObservers => _scopeObservers; + + void addScopeObserver(ScopeObserver scopeObserver) { + _scopeObservers.add(scopeObserver); + } + + final List _ignoredExceptionsForType = []; + + /// Ignored exception types. + List get ignoredExceptionsForType => _ignoredExceptionsForType; + + /// Adds exception type to the list of ignored exceptions. + void addExceptionFilterForType(Type exceptionType) { + _ignoredExceptionsForType.add(exceptionType); + } + + /// Check if [ignoredExceptionsForType] contains an exception. + bool containsIgnoredExceptionForType(dynamic exception) { + return exception != null && + _ignoredExceptionsForType.contains(exception.runtimeType); + } + + /// Enables Dart symbolication for stack traces in Flutter for Android and Cocoa. + /// + /// If true, the SDK will attempt to symbolicate Dart stack traces when + /// [Sentry.init] is used instead of `SentryFlutter.init`. This is useful + /// when native debug images are not available. + /// + /// Automatically set to `false` when using `SentryFlutter.init` on a platform + /// with a native integration (e.g. Android, iOS, ...). + bool enableDartSymbolication = true; + + @internal + late ClientReportRecorder recorder = NoOpClientReportRecorder(); + + /// List of strings/regex controlling to which outgoing requests + /// the SDK will attach tracing headers. + /// + /// By default the SDK will attach those headers to all outgoing + /// requests. If this option is provided, the SDK will match the + /// request URL of outgoing requests against the items in this + /// array, and only attach tracing headers if a match was found. + final List tracePropagationTargets = ['.*']; + + /// The idle time to wait until the transaction will be finished. + /// The transaction will use the end timestamp of the last finished span as + /// the endtime for the transaction. + /// + /// When set to null the transaction must be finished manually. + /// + /// The default is 3 seconds. + Duration? idleTimeout = Duration(seconds: 3); + + final _causeExtractorsByType = {}; + + final _stackTraceExtractorsByType = {}; + + /// Returns a previously added [ExceptionCauseExtractor] by type + ExceptionCauseExtractor? exceptionCauseExtractor(Type type) { + return _causeExtractorsByType[type]; + } + + /// Adds [ExceptionCauseExtractor] in order to extract inner exceptions + void addExceptionCauseExtractor(ExceptionCauseExtractor extractor) { + _causeExtractorsByType[extractor.exceptionType] = extractor; + } + + /// Returns a previously added [ExceptionStackTraceExtractor] by type + ExceptionStackTraceExtractor? exceptionStackTraceExtractor(Type type) { + return _stackTraceExtractorsByType[type]; + } + + /// Adds [ExceptionStackTraceExtractor] in order to extract inner exceptions + void addExceptionStackTraceExtractor(ExceptionStackTraceExtractor extractor) { + _stackTraceExtractorsByType[extractor.exceptionType] = extractor; + } + + /// Only for internal use. Changed SDK behaviour when set to true: + /// - Rethrow exceptions that occur in user provided closures + @internal + bool automatedTestMode = false; + + /// Errors that the SDK automatically collects, for example in + /// [SentryIsolate], have `level` [SentryLevel.fatal] set per default. + /// Settings this to `false` will set the `level` to [SentryLevel.error]. + bool markAutomaticallyCollectedErrorsAsFatal = true; + + /// Enables identification of exception types in obfuscated builds. + /// When true, the SDK will attempt to identify common exception types + /// to improve readability of obfuscated issue titles. + /// + /// If you already have events with obfuscated issue titles this will change grouping. + /// + /// Default: `true` + bool enableExceptionTypeIdentification = true; + + final List _exceptionTypeIdentifiers = []; + + List get exceptionTypeIdentifiers => + List.unmodifiable(_exceptionTypeIdentifiers); + + void addExceptionTypeIdentifierByIndex( + int index, ExceptionTypeIdentifier exceptionTypeIdentifier) { + _exceptionTypeIdentifiers.insert( + index, exceptionTypeIdentifier.withCache()); + } + + /// Adds an exception type identifier to the beginning of the list. + /// This ensures it is processed first and takes precedence over existing identifiers. + void prependExceptionTypeIdentifier( + ExceptionTypeIdentifier exceptionTypeIdentifier) { + addExceptionTypeIdentifierByIndex(0, exceptionTypeIdentifier); + } + + /// The Spotlight configuration. + /// Disabled by default. + /// ```dart + /// spotlight = Spotlight(enabled: true) + /// ``` + Spotlight spotlight = Spotlight(enabled: false); + + /// Configure a proxy to use for SDK API calls. + /// + /// On io platforms without native SDKs (dart, linux, windows), this will use + /// an 'IOClient' with inner 'HTTPClient' for http communication. + /// A http proxy will be set in returned for 'HttpClient.findProxy' in the + /// form 'PROXY your_host:your_port'. + /// When setting 'user' and 'pass', the 'HttpClient.addProxyCredentials' + /// method will be called with empty 'realm'. + /// + /// On Android & iOS, the proxy settings are handled by the native SDK. + /// iOS only supports http proxies, while macOS also supports socks. + SentryProxy? proxy; + + /// Whether to group exceptions hierarchically. + /// + /// If true, exceptions will be grouped hierarchically if possible. + /// + /// This is opt-in, as it can lead to existing exception beeing grouped as new ones. + bool groupExceptions = false; + + /// Enable to capture and send logs to Sentry. + /// + /// Disabled by default. + bool enableLogs = false; + + /// Enables adding the module in [SentryStackFrame.module]. + /// This option only has an effect in non-obfuscated builds. + /// Enabling this option may change grouping. + bool includeModuleInStackTrace = false; + + late final SentryLogger logger = SentryLogger(clock); + + @internal + SentryLogBatcher logBatcher = NoopLogBatcher(); + + SentryOptions({String? dsn, Platform? platform, RuntimeChecker? checker}) { + this.dsn = dsn; + if (platform != null) { + this.platform = platform; + } + if (checker != null) { + runtimeChecker = checker; + } + sdk = SdkVersion(name: sdkName(this.platform.isWeb), version: sdkVersion); + sdk.addPackage('pub:sentry', sdkVersion); + } + + @internal + SentryOptions.empty() { + sdk = SdkVersion(name: 'noop', version: sdkVersion); + } + + /// Adds an event processor + void addEventProcessor(EventProcessor eventProcessor) { + _eventProcessors.add(eventProcessor); + } + + /// Removes an event processor + void removeEventProcessor(EventProcessor eventProcessor) { + _eventProcessors.remove(eventProcessor); + } + + /// Adds an integration + void addIntegration(Integration integration) { + _integrations.add(integration); + } + + /// Adds an integration in the given index + void addIntegrationByIndex(int index, Integration integration) { + _integrations.insert(index, integration); + } + + /// Removes an integration + void removeIntegration(Integration integration) { + _integrations.remove(integration); + } + + /// Adds an inAppExclude + void addInAppExclude(String inAppInclude) { + _inAppExcludes.add(inAppInclude); + } + + /// Adds an inAppIncludes + void addInAppInclude(String inAppExclude) { + _inAppIncludes.add(inAppExclude); + } + + /// Returns if tracing should be enabled. If tracing is disabled, starting transactions returns + /// [NoOpSentrySpan]. + bool isTracingEnabled() { + return tracesSampleRate != null || tracesSampler != null; + } + + List get performanceCollectors => + _performanceCollectors; + final List _performanceCollectors = []; + + void addPerformanceCollector(PerformanceCollector collector) { + _performanceCollectors.add(collector); + } + + @internal + late SentryExceptionFactory exceptionFactory = SentryExceptionFactory(this); + + @internal + late SentryStackTraceFactory stackTraceFactory = + SentryStackTraceFactory(this); + + @visibleForTesting + void debugLog( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + developer.log( + '[${level.name}] $message', + level: level.toDartLogLevel(), + name: logger ?? 'sentry', + time: clock(), + error: exception, + stackTrace: stackTrace, + ); + } +} + +@visibleForTesting +void noOpLog( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, +}) {} + +/// This function is called with an SDK specific event object and can return a modified event +/// object or nothing to skip reporting the event +typedef BeforeSendCallback = FutureOr Function( + SentryEvent event, + Hint hint, +); + +/// This function is called with an SDK specific transaction object and can return a modified transaction +/// object or nothing to skip reporting the transaction +typedef BeforeSendTransactionCallback = FutureOr Function( + SentryTransaction transaction, + Hint hint, +); + +/// This function is called with an SDK specific breadcrumb object before the breadcrumb is added +/// to the scope. When nothing is returned from the function, the breadcrumb is dropped +typedef BeforeBreadcrumbCallback = Breadcrumb? Function( + Breadcrumb? breadcrumb, + Hint hint, +); + +/// This function is called right before a metric is about to be emitted. +/// Can return true to emit the metric, or false to drop it. +typedef BeforeMetricCallback = bool Function( + String key, { + Map? tags, +}); + +/// This function is called right before a log is about to be sent. +/// Can return a modified log or null to drop the log. +typedef BeforeSendLogCallback = FutureOr Function(SentryLog log); + +/// Used to provide timestamp for logging. +typedef ClockProvider = DateTime Function(); + +/// Logger callback to log useful debugging information if debug is enabled +typedef SdkLogCallback = void Function( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, +}); + +typedef TracesSamplerCallback = double? Function( + SentrySamplingContext samplingContext); diff --git a/packages/dart/lib/src/sentry_run_zoned_guarded.dart b/packages/dart/lib/src/sentry_run_zoned_guarded.dart new file mode 100644 index 0000000000..d2f47fbef5 --- /dev/null +++ b/packages/dart/lib/src/sentry_run_zoned_guarded.dart @@ -0,0 +1,118 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; + +import '../sentry.dart'; + +@internal +class SentryRunZonedGuarded { + /// Needed to check if we somehow caused a `print()` recursion + static var _isPrinting = false; + + static R? sentryRunZonedGuarded( + Hub hub, + R Function() body, + void Function(Object error, StackTrace stack)? onError, { + Map? zoneValues, + ZoneSpecification? zoneSpecification, + }) { + final sentryOnError = (exception, stackTrace) async { + final options = hub.options; + await _captureError(hub, options, exception, stackTrace); + + if (onError != null) { + onError(exception, stackTrace); + } + }; + + final userPrint = zoneSpecification?.print; + + final sentryZoneSpecification = ZoneSpecification.from( + zoneSpecification ?? ZoneSpecification(), + print: (self, parent, zone, line) { + final options = hub.options; + + if (userPrint != null) { + userPrint(self, parent, zone, line); + } + + if (!options.enablePrintBreadcrumbs || !hub.isEnabled) { + // early bail out, in order to better guard against the recursion + // as described below. + parent.print(zone, line); + return; + } + if (_isPrinting) { + // We somehow landed in a recursion. + // This happens for example if: + // - hub.addBreadcrumb() called print() itself + // - This happens for example if hub.isEnabled == false and + // options.logger == _debugLogger + // + // Anyway, in order to not cause a stack overflow due to recursion + // we drop any further print() call while adding a breadcrumb. + parent.print( + zone, + 'Recursion during print() call.' + 'Abort adding print() call as Breadcrumb.', + ); + return; + } + + try { + _isPrinting = true; + unawaited(hub.addBreadcrumb( + Breadcrumb.console( + message: line, + level: SentryLevel.debug, + ), + )); + parent.print(zone, line); + } finally { + _isPrinting = false; + } + }, + ); + return runZonedGuarded( + body, + sentryOnError, + zoneValues: zoneValues, + zoneSpecification: sentryZoneSpecification, + ); + } + + static Future _captureError( + Hub hub, + SentryOptions options, + Object exception, + StackTrace stackTrace, + ) async { + options.log( + SentryLevel.error, + 'Uncaught zone error', + logger: 'sentry.runZonedGuarded', + exception: exception, + stackTrace: stackTrace, + ); + + // runZonedGuarded doesn't crash the app, but is not handled by the user. + final mechanism = Mechanism(type: 'runZonedGuarded', handled: false); + final throwableMechanism = ThrowableMechanism(mechanism, exception); + + final event = SentryEvent( + throwable: throwableMechanism, + level: options.markAutomaticallyCollectedErrorsAsFatal + ? SentryLevel.fatal + : SentryLevel.error, + timestamp: hub.options.clock(), + ); + + // marks the span status if none to `internal_error` in case there's an + // unhandled error + hub.configureScope( + (scope) => scope.span?.status ??= const SpanStatus.internalError(), + ); + + await hub.captureEvent(event, stackTrace: stackTrace); + } +} diff --git a/packages/dart/lib/src/sentry_sampling_context.dart b/packages/dart/lib/src/sentry_sampling_context.dart new file mode 100644 index 0000000000..d746e4e38e --- /dev/null +++ b/packages/dart/lib/src/sentry_sampling_context.dart @@ -0,0 +1,21 @@ +import 'package:meta/meta.dart'; + +import 'tracing.dart'; +import 'sentry_options.dart'; + +/// Context used by [TracesSamplerCallback] to determine if transaction +/// is going to be sampled. +@immutable +class SentrySamplingContext { + final SentryTransactionContext _transactionContext; + final Map _customSamplingContext; + + SentrySamplingContext(this._transactionContext, this._customSamplingContext); + + /// The Transaction context + SentryTransactionContext get transactionContext => _transactionContext; + + /// The given sampling context + Map get customSamplingContext => + Map.unmodifiable(_customSamplingContext); +} diff --git a/packages/dart/lib/src/sentry_span_context.dart b/packages/dart/lib/src/sentry_span_context.dart new file mode 100644 index 0000000000..aea17f646e --- /dev/null +++ b/packages/dart/lib/src/sentry_span_context.dart @@ -0,0 +1,69 @@ +import 'package:meta/meta.dart'; + +import '../sentry.dart'; + +class SentrySpanContext { + /// Determines which trace the Span belongs to + late SentryId traceId; + + /// Span id + late SpanId spanId; + + /// Id of a parent span + SpanId? parentSpanId; + + /// Short code identifying the type of operation the span is measuring + String operation; + + /// Longer description of the span's operation, which uniquely identifies the span but is + /// consistent across instances of the span. + String? description; + + /// The origin of the span indicates what created the span. + /// + /// Gets set by the SDK. It is not expected to be set manually by users. + /// + /// See https://develop.sentry.dev/sdk/performance/trace-origin + String? origin; + + /// Item encoded as JSON + Map toJson() { + return { + 'span_id': spanId.toString(), + 'trace_id': traceId.toString(), + 'op': operation, + if (parentSpanId != null) 'parent_span_id': parentSpanId.toString(), + if (description != null) 'description': description, + if (origin != null) 'origin': origin, + }; + } + + SentrySpanContext({ + SentryId? traceId, + SpanId? spanId, + this.parentSpanId, + required this.operation, + this.description, + this.origin, + }) : traceId = traceId ?? SentryId.newId(), + spanId = spanId ?? SpanId.newId(); + + @internal + SentryTraceContext toTraceContext({ + bool? sampled, + SpanStatus? status, + Map? data, + }) { + return SentryTraceContext( + operation: operation, + traceId: traceId, + spanId: spanId, + description: description, + parentSpanId: parentSpanId, + sampled: sampled, + status: status, + origin: origin, + data: data, + ); + } +} diff --git a/packages/dart/lib/src/sentry_span_interface.dart b/packages/dart/lib/src/sentry_span_interface.dart new file mode 100644 index 0000000000..a377310b79 --- /dev/null +++ b/packages/dart/lib/src/sentry_span_interface.dart @@ -0,0 +1,90 @@ +import 'package:meta/meta.dart'; + +import 'hint.dart'; +import 'protocol.dart'; +import 'tracing.dart'; + +/// Represents performance monitoring Span. +abstract class ISentrySpan { + /// Starts a child Span. + ISentrySpan startChild( + String operation, { + String? description, + DateTime? startTimestamp, + }); + + /// Sets the tag on span or transaction. + void setTag(String key, String value); + + /// Removes the tag on span or transaction. + void removeTag(String key); + + /// Sets extra data on span or transaction. + void setData(String key, dynamic value); + + /// Removes extra data on span or transaction. + void removeData(String key); + + /// Sets span timestamp marking this span as finished. + Future finish({ + SpanStatus? status, + DateTime? endTimestamp, + Hint? hint, + }) async {} + + /// Gets span status. + SpanStatus? get status; + + /// Sets span status. + set status(SpanStatus? status); + + /// Gets the span context. + SentrySpanContext get context; + + /// Gets the span origin + String? get origin; + + /// Sets span origin. + /// + /// Gets set by the SDK. It is not expected to be set manually by users. + /// + /// See https://develop.sentry.dev/sdk/performance/trace-origin + set origin(String? origin); + + /// Returns the end timestamp if finished + DateTime? get endTimestamp; + + /// Returns the star timestamp + DateTime get startTimestamp; + + /// Returns true if span is finished + bool get finished; + + /// Returns the associated error + dynamic get throwable; + + /// Associated the error with the span + set throwable(dynamic throwable); + + @internal + SentryTracesSamplingDecision? get samplingDecision; + + /// Returns the trace information that could be sent as a sentry-trace header. + SentryTraceHeader toSentryTrace(); + + /// Set observed measurement for this span or transaction. + void setMeasurement( + String name, + num value, { + SentryMeasurementUnit? unit, + }); + + /// Returns the baggage that can be sent as "baggage" header. + SentryBaggageHeader? toBaggageHeader(); + + /// Returns the trace context. + SentryTraceContextHeader? traceContext(); + + @internal + void scheduleFinish(); +} diff --git a/packages/dart/lib/src/sentry_stack_trace_factory.dart b/packages/dart/lib/src/sentry_stack_trace_factory.dart new file mode 100644 index 0000000000..2e89dd7c36 --- /dev/null +++ b/packages/dart/lib/src/sentry_stack_trace_factory.dart @@ -0,0 +1,222 @@ +import 'package:meta/meta.dart'; +import 'package:stack_trace/stack_trace.dart'; + +import 'origin.dart'; +import 'protocol.dart'; +import 'sentry_options.dart'; +import 'utils/stacktrace_utils.dart'; + +/// converts [StackTrace] to [SentryStackFrame]s +class SentryStackTraceFactory { + final SentryOptions _options; + + static final _frameRegex = RegExp(r'^\s*#', multiLine: true); + static final _baseAddrRegex = RegExp(r'isolate_dso_base[:=] *([A-Fa-f0-9]+)'); + static final SentryStackFrame _asynchronousGapFrameJson = + SentryStackFrame(absPath: ''); + + SentryStackTraceFactory(this._options); + + /// returns the [SentryStackFrame] list from a stackTrace ([StackTrace] or [String]) + @Deprecated('Use parse() instead') + List getStackFrames(dynamic stackTrace) { + return parse(stackTrace).frames; + } + + SentryStackTrace parse(dynamic stackTrace, {bool? removeSentryFrames}) { + final parsed = _parseStackTrace(stackTrace); + final frames = []; + var onlyAsyncGap = true; + + for (var t = 0; t < parsed.traces.length; t += 1) { + final trace = parsed.traces[t]; + + // NOTE: We want to keep the Sentry frames for SDK crash detection + // this does not affect grouping since they're not marked as inApp + // only exception if there was no stack trace, we remove them + for (final frame in trace.frames) { + var stackTraceFrame = encodeStackTraceFrame(frame); + + if (stackTraceFrame != null) { + if (removeSentryFrames == true && + (stackTraceFrame.package == 'sentry' || + stackTraceFrame.package == 'sentry_flutter')) { + continue; + } + frames.add(stackTraceFrame); + onlyAsyncGap = false; + } + } + + // fill asynchronous gap + if (t < parsed.traces.length - 1) { + frames.add(_asynchronousGapFrameJson); + } + } + + return SentryStackTrace( + frames: onlyAsyncGap ? [] : frames.reversed.toList(), + baseAddr: parsed.baseAddr, + buildId: parsed.buildId, + ); + } + + _StackInfo _parseStackTrace(dynamic stackTrace) { + if (stackTrace is Chain) { + return _StackInfo(stackTrace.traces); + } else if (stackTrace is Trace) { + return _StackInfo([stackTrace]); + } + + // We need to convert to string and split the headers manually, otherwise + // they end up in the final stack trace as "unparsed" lines. + // Note: [Chain.forTrace] would call [stackTrace.toString()] too. + if (stackTrace is StackTrace) { + stackTrace = stackTrace.toString(); + } + + if (stackTrace is String) { + // Remove headers (everything before the first line starting with '#'). + // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** + // pid: 19226, tid: 6103134208, name io.flutter.ui + // os: macos arch: arm64 comp: no sim: no + // build_id: 'bca64abfdfcc84d231bb8f1ccdbfbd8d' + // isolate_dso_base: 10fa20000, vm_dso_base: 10fa20000 + // isolate_instructions: 10fa27070, vm_instructions: 10fa21e20 + // #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 + // #01 abs 000000723d637527 _kDartIsolateSnapshotInstructions+0x1e5527 + + final startOffset = _frameRegex.firstMatch(stackTrace)?.start ?? 0; + final chain = Chain.parse( + startOffset == 0 ? stackTrace : stackTrace.substring(startOffset)); + final info = _StackInfo(chain.traces); + info.buildId = buildIdRegex.firstMatch(stackTrace)?.group(1); + info.baseAddr = _baseAddrRegex.firstMatch(stackTrace)?.group(1); + if (info.baseAddr != null) { + info.baseAddr = '0x${info.baseAddr}'; + } + return info; + } + return _StackInfo([]); + } + + /// converts [Frame] to [SentryStackFrame] + @visibleForTesting + SentryStackFrame? encodeStackTraceFrame(Frame frame) { + final member = frame.member; + + if (frame is UnparsedFrame && member != null) { + // if --split-debug-info is enabled, that's what we see: + // #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 + + // we are only interested on the #01, 02... items which contains the 'abs' addresses. + final match = absRegex.firstMatch(member); + if (match != null) { + return SentryStackFrame( + instructionAddr: '0x${match.group(1)!}', + // 'native' triggers the [LoadImageListIntegration] and server-side symbolication + platform: 'native', + ); + } + + // We shouldn't get here. If we do, it means there's likely an issue in + // the parsing so let's fall back and post a stack trace as is, so that at + // least we get an indication something's wrong and are able to fix it. + _options.log(SentryLevel.debug, "Failed to parse stack frame: $member"); + } + + final platform = _options.platform.isWeb ? 'javascript' : 'dart'; + final fileName = + frame.uri.pathSegments.isNotEmpty ? frame.uri.pathSegments.last : null; + final abs = '$eventOrigin${_absolutePathForCrashReport(frame)}'; + + final includeModule = + frame.package != null && _options.includeModuleInStackTrace; + + var sentryStackFrame = SentryStackFrame( + absPath: abs, + function: member, + // https://docs.sentry.io/development/sdk-dev/features/#in-app-frames + inApp: _isInApp(frame), + fileName: fileName, + package: frame.package, + platform: platform, + module: includeModule + ? frame.uri.pathSegments + .sublist(0, frame.uri.pathSegments.length - 1) + .join('/') + : null, + ); + + final line = frame.line; + if (line != null && line >= 0) { + sentryStackFrame.lineNo = frame.line; + } + + final column = frame.column; + if (column != null && column >= 0) { + sentryStackFrame.colNo = frame.column; + } + return sentryStackFrame; + } + + /// A stack frame's code path may be one of "file:", "dart:" and "package:". + /// + /// Absolute file paths may contain personally identifiable information, and + /// therefore are stripped to only send the base file name. For example, + /// "/foo/bar/baz.dart" is reported as "baz.dart". + /// + /// "dart:" and "package:" imports are always relative and are OK to send in + /// full. + String _absolutePathForCrashReport(Frame frame) { + if (frame.uri.scheme != 'dart' && + frame.uri.scheme != 'package' && + frame.uri.pathSegments.isNotEmpty) { + return frame.uri.pathSegments.last; + } + + return frame.uri.toString(); + } + + /// whether this frame comes from the app and not from Dart core or 3rd party librairies + bool _isInApp(Frame frame) { + final scheme = frame.uri.scheme; + + if (scheme.isEmpty) { + // Early bail out. + return _options.considerInAppFramesByDefault; + } + // The following code depends on the scheme being set. + + final package = frame.package; + if (package != null) { + if (_options.inAppIncludes.contains(package)) { + return true; + } + + if (_options.inAppExcludes.contains(package)) { + return false; + } + } + + if (frame.isCore) { + // This is a Dart frame + return false; + } + + if (frame.package == 'flutter') { + // This is a Flutter frame + return false; + } + + return _options.considerInAppFramesByDefault; + } +} + +class _StackInfo { + String? baseAddr; + String? buildId; + final List traces; + + _StackInfo(this.traces); +} diff --git a/packages/dart/lib/src/sentry_template_string.dart b/packages/dart/lib/src/sentry_template_string.dart new file mode 100644 index 0000000000..fa9c346443 --- /dev/null +++ b/packages/dart/lib/src/sentry_template_string.dart @@ -0,0 +1,43 @@ +class SentryTemplateString { + SentryTemplateString(this.template, this.arguments); + + final String template; + final List arguments; + static final _regex = RegExp(r'%(?:%|s)'); + + String format() { + assert(arguments.isNotEmpty, 'No arguments provided for template.'); + + int argIndex = 0; + var foundPlaceholders = false; + final string = template.replaceAllMapped(_regex, (Match m) { + final token = m[0]; + if (token == '%%') { + // `%%` → literal `%` + return '%'; + } + foundPlaceholders = true; + + // `%s` → next argument or empty if none left + if (argIndex < arguments.length) { + final value = arguments[argIndex++]; + try { + return value.toString(); + } catch (e) { + // If toString() fails, return empty string + return ''; + } + } + return ''; + }); + + assert(foundPlaceholders, 'No placeholders provided in template.'); + + return string; + } + + @override + String toString() { + return format(); + } +} diff --git a/packages/dart/lib/src/sentry_trace_context_header.dart b/packages/dart/lib/src/sentry_trace_context_header.dart new file mode 100644 index 0000000000..f94f772dc7 --- /dev/null +++ b/packages/dart/lib/src/sentry_trace_context_header.dart @@ -0,0 +1,117 @@ +import 'package:meta/meta.dart'; + +import 'protocol/access_aware_map.dart'; +import 'protocol/sentry_id.dart'; +import 'sentry_baggage.dart'; +import 'sentry_options.dart'; + +class SentryTraceContextHeader { + SentryTraceContextHeader( + this.traceId, + this.publicKey, { + this.release, + this.environment, + this.userId, + this.transaction, + this.sampleRate, + this.sampleRand, + this.sampled, + this.unknown, + this.replayId, + }); + + final SentryId traceId; + final String publicKey; + final String? release; + final String? environment; + final String? userId; + final String? transaction; + final String? sampleRate; + final String? sampleRand; + final String? sampled; + + @internal + final Map? unknown; + + @internal + SentryId? replayId; + + /// Deserializes a [SentryTraceContextHeader] from JSON [Map]. + factory SentryTraceContextHeader.fromJson(Map data) { + final json = AccessAwareMap(data); + return SentryTraceContextHeader( + SentryId.fromId(json['trace_id']), + json['public_key'], + release: json['release'], + environment: json['environment'], + userId: json['user_id'], + transaction: json['transaction'], + sampleRate: json['sample_rate'], + sampled: json['sampled'], + replayId: + json['replay_id'] == null ? null : SentryId.fromId(json['replay_id']), + unknown: json.notAccessed(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. + Map toJson() { + return { + ...?unknown, + 'trace_id': traceId.toString(), + 'public_key': publicKey, + if (release != null) 'release': release, + if (environment != null) 'environment': environment, + if (userId != null) 'user_id': userId, + if (transaction != null) 'transaction': transaction, + if (sampleRate != null) 'sample_rate': sampleRate, + if (sampled != null) 'sampled': sampled, + if (replayId != null) 'replay_id': replayId.toString(), + }; + } + + SentryBaggage toBaggage({ + SdkLogCallback? log, + }) { + final baggage = SentryBaggage({}, log: log); + baggage.setTraceId(traceId.toString()); + baggage.setPublicKey(publicKey); + + if (release != null) { + baggage.setRelease(release!); + } + if (environment != null) { + baggage.setEnvironment(environment!); + } + if (userId != null) { + baggage.setUserId(userId!); + } + if (transaction != null) { + baggage.setTransaction(transaction!); + } + if (sampleRate != null) { + baggage.setSampleRate(sampleRate!); + } + if (sampleRand != null) { + baggage.setSampleRand(sampleRand!); + } + if (sampled != null) { + baggage.setSampled(sampled!); + } + if (replayId != null) { + baggage.setReplayId(replayId.toString()); + } + return baggage; + } + + factory SentryTraceContextHeader.fromBaggage(SentryBaggage baggage) { + return SentryTraceContextHeader( + // TODO: implement and use proper get methods here + SentryId.fromId(baggage.get('sentry-trace_id').toString()), + baggage.get('sentry-public_key').toString(), + release: baggage.get('sentry-release'), + environment: baggage.get('sentry-environment'), + replayId: baggage.getReplayId(), + ); + } +} diff --git a/packages/dart/lib/src/sentry_trace_origins.dart b/packages/dart/lib/src/sentry_trace_origins.dart new file mode 100644 index 0000000000..23359bf9f2 --- /dev/null +++ b/packages/dart/lib/src/sentry_trace_origins.dart @@ -0,0 +1,30 @@ +import 'package:meta/meta.dart'; + +@internal +class SentryTraceOrigins { + static const manual = 'manual'; + + static const autoNavigationRouteObserver = 'auto.navigation.route_observer'; + static const autoHttpHttp = 'auto.http.http'; + static const autoHttpDioHttpClientAdapter = + 'auto.http.dio.http_client_adapter'; + static const autoHttpDioTransformer = 'auto.http.dio.transformer'; + static const autoFile = 'auto.file'; + static const autoFileAssetBundle = 'auto.file.asset_bundle'; + static const autoDbSqfliteOpenDatabase = 'auto.db.sqflite.open_database'; + static const autoDbSqfliteBatch = 'auto.db.sqflite.batch'; + static const autoDbSqfliteDatabase = 'auto.db.sqflite.database'; + static const autoDbSqfliteDatabaseExecutor = + 'auto.db.sqflite.database_executor'; + static const autoDbSqfliteDatabaseFactory = + 'auto.db.sqflite.database_factory'; + static const autoDbIsar = 'auto.db.isar'; + static const autoDbIsarCollection = 'auto.db.isar.collection'; + static const autoDbHive = 'auto.db.hive'; + static const autoDbHiveBoxBase = 'auto.db.hive.box_base'; + static const autoDbHiveLazyBox = 'auto.db.hive.lazy_box'; + static const autoDbHiveBoxCollection = 'auto.db.hive.box_collection'; + static const autoDbDriftQueryInterceptor = 'auto.db.drift.query.interceptor'; + static const autoUiTimeToDisplay = 'auto.ui.time_to_display'; + static const manualUiTimeToDisplay = 'manual.ui.time_to_display'; +} diff --git a/packages/dart/lib/src/sentry_tracer.dart b/packages/dart/lib/src/sentry_tracer.dart new file mode 100644 index 0000000000..1cc618a716 --- /dev/null +++ b/packages/dart/lib/src/sentry_tracer.dart @@ -0,0 +1,438 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; + +import '../sentry.dart'; +import 'profiling.dart'; +import 'sentry_tracer_finish_status.dart'; +import 'utils/sample_rate_format.dart'; + +@internal +class SentryTracer extends ISentrySpan { + final Hub _hub; + late bool _waitForChildren; + late String name; + + late final SentrySpan _rootSpan; + final List _children = []; + final Map _extra = {}; + + final Map _measurements = {}; + Map get measurements => _measurements; + + Timer? _autoFinishAfterTimer; + Duration? _autoFinishAfter; + + @visibleForTesting + Timer? get autoFinishAfterTimer => _autoFinishAfterTimer; + + OnTransactionFinish? _onFinish; + var _finishStatus = SentryTracerFinishStatus.notFinishing(); + late final bool _trimEnd; + + late SentryTransactionNameSource transactionNameSource; + + SentryTraceContextHeader? _sentryTraceContextHeader; + + // Profiler attached to this tracer. + late final SentryProfiler? profiler; + + // Resulting profile, after it has been collected. This is later used by + // SentryClient to attach as an envelope item when sending the transaction. + SentryProfileInfo? profileInfo; + + /// If [waitForChildren] is true, this transaction will not finish until all + /// its children are finished. + /// + /// When [autoFinishAfter] is provided, started transactions will + /// automatically be finished after this duration. + /// + /// If [trimEnd] is true, sets the end timestamp of the transaction to the + /// highest timestamp of child spans, trimming the duration of the + /// transaction. This is useful to discard extra time in the transaction that + /// is not accounted for in child spans, like what happens in the + /// [SentryNavigatorObserver](https://pub.dev/documentation/sentry_flutter/latest/sentry_flutter/SentryNavigatorObserver-class.html) + /// idle transactions, where we finish the transaction after a given + /// "idle time" and we don't want this "idle time" to be part of the transaction. + SentryTracer( + SentryTransactionContext transactionContext, + this._hub, { + DateTime? startTimestamp, + bool waitForChildren = false, + Duration? autoFinishAfter, + bool trimEnd = false, + OnTransactionFinish? onFinish, + this.profiler, + }) { + _rootSpan = SentrySpan( + this, + transactionContext, + _hub, + samplingDecision: transactionContext.samplingDecision, + startTimestamp: startTimestamp, + isRootSpan: true, + ); + _waitForChildren = waitForChildren; + _autoFinishAfter = autoFinishAfter; + + _scheduleTimer(); + name = transactionContext.name; + // always default to custom if not provided + transactionNameSource = transactionContext.transactionNameSource ?? + SentryTransactionNameSource.custom; + _trimEnd = trimEnd; + _onFinish = onFinish; + + for (final collector in _hub.options.performanceCollectors) { + if (collector is PerformanceContinuousCollector) { + collector.onSpanStarted(_rootSpan); + } + } + } + + @override + Future finish( + {SpanStatus? status, DateTime? endTimestamp, Hint? hint}) async { + final commonEndTimestamp = endTimestamp ?? _hub.options.clock(); + _autoFinishAfterTimer?.cancel(); + _finishStatus = SentryTracerFinishStatus.finishing(status); + if (_rootSpan.finished) { + return; + } + if (_waitForChildren && !_haveAllChildrenFinished()) { + return; + } + try { + _rootSpan.status ??= status; + + // remove span where its endTimestamp is before startTimestamp + _children.removeWhere( + (span) => !_hasSpanSuitableTimestamps(span, commonEndTimestamp)); + + var _rootEndTimestamp = commonEndTimestamp; + + // Trim the end timestamp of the transaction to the very last timestamp of child spans + if (_trimEnd && children.isNotEmpty) { + DateTime? latestEndTime; + + for (final child in children) { + final childEndTimestamp = child.endTimestamp; + if (childEndTimestamp != null) { + if (latestEndTime == null || + childEndTimestamp.isAfter(latestEndTime)) { + latestEndTime = child.endTimestamp; + } + } + } + + if (latestEndTime != null) { + _rootEndTimestamp = latestEndTime; + } + } + + // the callback should run before because if the span is finished, + // we cannot attach data, its immutable after being finished. + final finish = _onFinish?.call(this); + if (finish is Future) { + await finish; + } + await _rootSpan.finish(endTimestamp: _rootEndTimestamp, hint: hint); + + // remove from scope + await _hub.configureScope((scope) { + if (scope.span == this) { + scope.span = null; + } + }); + + // if it's an idle transaction which has no children, we drop it to save user's quota + if (children.isEmpty && _autoFinishAfter != null) { + return; + } + + final transaction = SentryTransaction(this); + transaction.measurements.addAll(_measurements); + + profileInfo = (status == null || status == SpanStatus.ok()) + ? await profiler?.finishFor(transaction) + : null; + + await _hub.captureTransaction( + transaction, + traceContext: traceContext(), + hint: hint, + ); + } finally { + profiler?.dispose(); + } + } + + @override + void removeData(String key) { + if (finished) { + return; + } + + _extra.remove(key); + } + + @override + void removeTag(String key) { + if (finished) { + return; + } + + _rootSpan.removeTag(key); + } + + @override + void setData(String key, dynamic value) { + if (finished) { + return; + } + + _extra[key] = value; + } + + @override + void setTag(String key, String value) { + if (finished) { + return; + } + + _rootSpan.setTag(key, value); + } + + @override + ISentrySpan startChild( + String operation, { + String? description, + DateTime? startTimestamp, + }) { + if (finished) { + return NoOpSentrySpan(); + } + + if (children.length >= _hub.options.maxSpans) { + _hub.options.log( + SentryLevel.warning, + 'Span operation: $operation, description: $description dropped due to limit reached. Returning NoOpSpan.', + ); + return NoOpSentrySpan(); + } + + return _rootSpan.startChild( + operation, + description: description, + startTimestamp: startTimestamp, + ); + } + + ISentrySpan startChildWithParentSpanId( + SpanId parentSpanId, + String operation, { + String? description, + DateTime? startTimestamp, + }) { + if (finished) { + return NoOpSentrySpan(); + } + + // reset the timer if a new child is added + _scheduleTimer(); + + if (children.length >= _hub.options.maxSpans) { + _hub.options.log( + SentryLevel.warning, + 'Span operation: $operation, description: $description dropped due to limit reached. Returning NoOpSpan.', + ); + return NoOpSentrySpan(); + } + + final context = SentrySpanContext( + traceId: _rootSpan.context.traceId, + parentSpanId: parentSpanId, + operation: operation, + description: description); + + final child = SentrySpan( + this, + context, + _hub, + samplingDecision: _rootSpan.samplingDecision, + startTimestamp: startTimestamp, + finishedCallback: _finishedCallback, + ); + + _children.add(child); + + for (final collector in _hub.options.performanceCollectors) { + if (collector is PerformanceContinuousCollector) { + collector.onSpanStarted(child); + } + } + + return child; + } + + Future _finishedCallback({ + DateTime? endTimestamp, + Hint? hint, + }) async { + final finishStatus = _finishStatus; + if (finishStatus.finishing) { + await finish( + status: finishStatus.status, + endTimestamp: endTimestamp, + hint: hint, + ); + } + } + + @override + SpanStatus? get status => _rootSpan.status; + + @override + SentrySpanContext get context => _rootSpan.context; + + @override + String? get origin => _rootSpan.origin; + + @override + set origin(String? origin) => _rootSpan.origin = origin; + + @override + DateTime get startTimestamp => _rootSpan.startTimestamp; + + @override + DateTime? get endTimestamp => _rootSpan.endTimestamp; + + Map get data => Map.unmodifiable(_extra); + + @override + bool get finished => _rootSpan.finished; + + List get children => _children; + + @override + dynamic get throwable => _rootSpan.throwable; + + @override + set throwable(throwable) => _rootSpan.throwable = throwable; + + @override + set status(SpanStatus? status) => _rootSpan.status = status; + + Map get tags => _rootSpan.tags; + + @override + SentryTraceHeader toSentryTrace() => _rootSpan.toSentryTrace(); + + bool _haveAllChildrenFinished() { + for (final child in children) { + if (!child.finished) { + return false; + } + } + return true; + } + + bool _hasSpanSuitableTimestamps( + SentrySpan span, DateTime endTimestampCandidate) => + !span.startTimestamp + .isAfter((span.endTimestamp ?? endTimestampCandidate)); + + @override + void setMeasurement(String name, num value, {SentryMeasurementUnit? unit}) { + if (finished) { + _hub.options.log(SentryLevel.debug, + "The tracer is already finished. Measurement $name cannot be set"); + return; + } + _measurements[name] = SentryMeasurement(name, value, unit: unit); + } + + void setMeasurementFromChild(String name, num value, + {SentryMeasurementUnit? unit}) { + // We don't want to overwrite span measurement, if it comes from a child. + if (!_measurements.containsKey(name)) { + setMeasurement(name, value, unit: unit); + } + } + + @override + SentryBaggageHeader? toBaggageHeader() { + final context = traceContext(); + + if (context != null) { + final baggage = context.toBaggage(log: _hub.options.log); + return SentryBaggageHeader.fromBaggage(baggage); + } + return null; + } + + @override + SentryTraceContextHeader? traceContext() { + // TODO: freeze context after 1st envelope or outgoing HTTP request + if (_sentryTraceContextHeader != null) { + return _sentryTraceContextHeader; + } + + _sentryTraceContextHeader = SentryTraceContextHeader( + _rootSpan.context.traceId, + _hub.options.parsedDsn.publicKey, + release: _hub.options.release, + environment: _hub.options.environment, + userId: null, // because of PII not sending it for now + transaction: + _isHighQualityTransactionName(transactionNameSource) ? name : null, + sampleRate: _sampleRateToString(_rootSpan.samplingDecision?.sampleRate), + sampleRand: _sampleRandToString(_rootSpan.samplingDecision?.sampleRand), + sampled: _rootSpan.samplingDecision?.sampled.toString(), + ); + + return _sentryTraceContextHeader; + } + + String? _sampleRateToString(double? sampleRate) { + if (!isValidSampleRate(sampleRate)) { + return null; + } + return sampleRate != null ? SampleRateFormat().format(sampleRate) : null; + } + + String? _sampleRandToString(double? sampleRand) { + if (!isValidSampleRand(sampleRand)) { + return null; + } + return sampleRand != null ? SampleRateFormat().format(sampleRand) : null; + } + + bool _isHighQualityTransactionName(SentryTransactionNameSource source) { + return source != SentryTransactionNameSource.url; + } + + @override + SentryTracesSamplingDecision? get samplingDecision => + _rootSpan.samplingDecision; + + @override + void scheduleFinish() { + if (finished) { + return; + } + if (_autoFinishAfterTimer != null) { + _scheduleTimer(); + } + } + + void _scheduleTimer() { + final autoFinishAfter = _autoFinishAfter; + if (autoFinishAfter != null) { + _autoFinishAfterTimer?.cancel(); + _autoFinishAfterTimer = Timer(autoFinishAfter, () async { + await finish(status: status ?? SpanStatus.ok()); + }); + } + } +} diff --git a/packages/dart/lib/src/sentry_tracer_finish_status.dart b/packages/dart/lib/src/sentry_tracer_finish_status.dart new file mode 100644 index 0000000000..eb0027d538 --- /dev/null +++ b/packages/dart/lib/src/sentry_tracer_finish_status.dart @@ -0,0 +1,15 @@ +import 'package:meta/meta.dart'; + +import '../sentry.dart'; + +@internal +class SentryTracerFinishStatus { + final bool finishing; + final SpanStatus? status; + + SentryTracerFinishStatus.finishing(this.status) : finishing = true; + + SentryTracerFinishStatus.notFinishing() + : finishing = false, + status = null; +} diff --git a/packages/dart/lib/src/sentry_traces_sampler.dart b/packages/dart/lib/src/sentry_traces_sampler.dart new file mode 100644 index 0000000000..3e1807b015 --- /dev/null +++ b/packages/dart/lib/src/sentry_traces_sampler.dart @@ -0,0 +1,87 @@ +import 'dart:math'; + +import 'package:meta/meta.dart'; + +import '../sentry.dart'; + +@internal +class SentryTracesSampler { + final SentryOptions _options; + final Random _random; + + SentryTracesSampler( + this._options, { + Random? random, + }) : _random = random ?? Random() { + if (_options.tracesSampler != null && _options.tracesSampleRate != null) { + _options.log(SentryLevel.warning, + 'Both tracesSampler and traceSampleRate are set. tracesSampler will take precedence and fallback to traceSampleRate if it returns null.'); + } + } + + SentryTracesSamplingDecision sample( + SentrySamplingContext samplingContext, + double sampleRand, + ) { + final samplingDecision = + samplingContext.transactionContext.samplingDecision; + if (samplingDecision != null) { + return samplingDecision; + } + + final tracesSampler = _options.tracesSampler; + if (tracesSampler != null) { + try { + final sampleRate = tracesSampler(samplingContext); + if (sampleRate != null) { + return _makeSampleDecision(sampleRate, sampleRand); + } + } catch (exception, stackTrace) { + _options.log( + SentryLevel.error, + 'The tracesSampler callback threw an exception', + exception: exception, + stackTrace: stackTrace, + ); + if (_options.automatedTestMode) { + rethrow; + } + } + } + + final parentSamplingDecision = + samplingContext.transactionContext.parentSamplingDecision; + if (parentSamplingDecision != null) { + return parentSamplingDecision; + } + + double? optionsRate = _options.tracesSampleRate; + if (optionsRate != null) { + return _makeSampleDecision(optionsRate, sampleRand); + } + + return SentryTracesSamplingDecision(false); + } + + bool sampleProfiling(SentryTracesSamplingDecision tracesSamplingDecision) { + double? optionsRate = _options.profilesSampleRate; + if (optionsRate == null || !tracesSamplingDecision.sampled) { + return false; + } + return _isSampled(optionsRate); + } + + SentryTracesSamplingDecision _makeSampleDecision( + double sampleRate, + double sampleRand, + ) { + final sampled = _isSampled(sampleRate, sampleRand: sampleRand); + return SentryTracesSamplingDecision(sampled, + sampleRate: sampleRate, sampleRand: sampleRand); + } + + bool _isSampled(double sampleRate, {double? sampleRand}) { + final rand = sampleRand ?? _random.nextDouble(); + return rand <= sampleRate; + } +} diff --git a/packages/dart/lib/src/sentry_traces_sampling_decision.dart b/packages/dart/lib/src/sentry_traces_sampling_decision.dart new file mode 100644 index 0000000000..802d27f832 --- /dev/null +++ b/packages/dart/lib/src/sentry_traces_sampling_decision.dart @@ -0,0 +1,11 @@ +class SentryTracesSamplingDecision { + SentryTracesSamplingDecision( + this.sampled, { + this.sampleRate, + this.sampleRand, + }); + + final bool sampled; + final double? sampleRate; + final double? sampleRand; +} diff --git a/packages/dart/lib/src/sentry_transaction_context.dart b/packages/dart/lib/src/sentry_transaction_context.dart new file mode 100644 index 0000000000..5002cb9b40 --- /dev/null +++ b/packages/dart/lib/src/sentry_transaction_context.dart @@ -0,0 +1,81 @@ +import 'protocol.dart'; +import 'sentry_baggage.dart'; +import 'sentry_trace_origins.dart'; +import 'tracing.dart'; + +class SentryTransactionContext extends SentrySpanContext { + String name; + SentryTransactionNameSource? transactionNameSource; + SentryTracesSamplingDecision? samplingDecision; + SentryTracesSamplingDecision? parentSamplingDecision; + + SentryTransactionContext( + this.name, + String operation, { + super.description, + super.traceId, + super.spanId, + super.parentSpanId, + this.transactionNameSource, + this.samplingDecision, + this.parentSamplingDecision, + super.origin, + }) : super( + operation: operation, + ); + + factory SentryTransactionContext.fromSentryTrace( + String name, + String operation, + SentryTraceHeader traceHeader, { + SentryTransactionNameSource? transactionNameSource, + SentryBaggage? baggage, + }) { + final sampleRate = baggage?.getSampleRate(); + final sampleRand = baggage?.getSampleRand(); + return SentryTransactionContext( + name, + operation, + traceId: traceHeader.traceId, + parentSpanId: traceHeader.spanId, + parentSamplingDecision: traceHeader.sampled != null + ? SentryTracesSamplingDecision( + traceHeader.sampled!, + sampleRate: sampleRate, + sampleRand: sampleRand, + ) + : null, + transactionNameSource: + transactionNameSource ?? SentryTransactionNameSource.custom, + origin: SentryTraceOrigins.manual, + ); + } + + @Deprecated('Assign values directly to the instance.') + SentryTransactionContext copyWith({ + String? name, + String? operation, + String? description, + SentryTracesSamplingDecision? parentSamplingDecision, + SentryId? traceId, + SpanId? spanId, + SpanId? parentSpanId, + SentryTransactionNameSource? transactionNameSource, + SentryTracesSamplingDecision? samplingDecision, + String? origin, + }) => + SentryTransactionContext( + name ?? this.name, + operation ?? this.operation, + description: description ?? this.description, + parentSamplingDecision: + parentSamplingDecision ?? this.parentSamplingDecision, + traceId: traceId ?? this.traceId, + spanId: spanId ?? this.spanId, + parentSpanId: parentSpanId ?? this.parentSpanId, + transactionNameSource: + transactionNameSource ?? this.transactionNameSource, + samplingDecision: samplingDecision ?? this.samplingDecision, + origin: origin ?? this.origin, + ); +} diff --git a/packages/dart/lib/src/span_data_convention.dart b/packages/dart/lib/src/span_data_convention.dart new file mode 100644 index 0000000000..5979e6035a --- /dev/null +++ b/packages/dart/lib/src/span_data_convention.dart @@ -0,0 +1,10 @@ +class SpanDataConvention { + SpanDataConvention._(); + + static const totalFrames = 'frames.total'; + static const slowFrames = 'frames.slow'; + static const frozenFrames = 'frames.frozen'; + static const framesDelay = 'frames.delay'; + + // TODO: eventually add other data keys here as well +} diff --git a/packages/dart/lib/src/spotlight.dart b/packages/dart/lib/src/spotlight.dart new file mode 100644 index 0000000000..e4c387a30f --- /dev/null +++ b/packages/dart/lib/src/spotlight.dart @@ -0,0 +1,12 @@ +/// Spotlight configuration class. +class Spotlight { + /// Whether to enable Spotlight for local development. + bool enabled; + + /// The Spotlight Sidecar URL. + /// Defaults to http://10.0.2.2:8969/stream due to Emulator on Android. + /// Otherwise defaults to http://localhost:8969/stream. + String? url; + + Spotlight({required this.enabled, this.url}); +} diff --git a/packages/dart/lib/src/throwable_mechanism.dart b/packages/dart/lib/src/throwable_mechanism.dart new file mode 100644 index 0000000000..8a3932a701 --- /dev/null +++ b/packages/dart/lib/src/throwable_mechanism.dart @@ -0,0 +1,20 @@ +import 'protocol/mechanism.dart'; + +/// A decorator that holds a Mechanism related to the decorated Exception +class ThrowableMechanism implements Exception { + final Mechanism _mechanism; + final dynamic _throwable; + final bool? _snapshot; + + ThrowableMechanism( + this._mechanism, + this._throwable, { + bool? snapshot, + }) : _snapshot = snapshot; + + Mechanism get mechanism => _mechanism; + + dynamic get throwable => _throwable; + + bool? get snapshot => _snapshot; +} diff --git a/packages/dart/lib/src/tracing.dart b/packages/dart/lib/src/tracing.dart new file mode 100644 index 0000000000..bc13c0a768 --- /dev/null +++ b/packages/dart/lib/src/tracing.dart @@ -0,0 +1,10 @@ +export 'sentry_transaction_context.dart'; +export 'sentry_sampling_context.dart'; +export 'sentry_span_context.dart'; +export 'sentry_span_interface.dart'; +export 'noop_sentry_span.dart'; +export 'invalid_sentry_trace_header_exception.dart'; +export 'sentry_measurement.dart'; +export 'sentry_measurement_unit.dart'; +export 'sentry_trace_context_header.dart'; +export 'sentry_traces_sampling_decision.dart'; diff --git a/packages/dart/lib/src/transport/client_report_transport.dart b/packages/dart/lib/src/transport/client_report_transport.dart new file mode 100644 index 0000000000..13f518b020 --- /dev/null +++ b/packages/dart/lib/src/transport/client_report_transport.dart @@ -0,0 +1,56 @@ +import 'package:meta/meta.dart'; +import '../../sentry.dart'; +import '../sentry_envelope_header.dart'; +import 'rate_limiter.dart'; + +/// Decorator that handles attaching of client reports in tandem with rate +/// limiting. The rate limiter is optional. +@internal +class ClientReportTransport implements Transport { + final RateLimiter? _rateLimiter; + final SentryOptions _options; + final Transport _transport; + + ClientReportTransport(this._rateLimiter, this._options, this._transport); + + @visibleForTesting + RateLimiter? get rateLimiter => _rateLimiter; + + int _numberOfDroppedEnvelopes = 0; + + @visibleForTesting + int get numberOfDroppedEvents => _numberOfDroppedEnvelopes; + + @override + Future send(SentryEnvelope envelope) async { + final rateLimiter = _rateLimiter; + + SentryEnvelope? filteredEnvelope = envelope; + if (rateLimiter != null) { + filteredEnvelope = rateLimiter.filter(envelope); + } + if (filteredEnvelope == null) { + _numberOfDroppedEnvelopes += 1; + } + if (_numberOfDroppedEnvelopes >= 10) { + // Create new envelope that could only contain client reports + filteredEnvelope = SentryEnvelope( + SentryEnvelopeHeader(SentryId.newId(), _options.sdk), + [], + ); + } + if (filteredEnvelope == null) { + return SentryId.empty(); + } + _numberOfDroppedEnvelopes = 0; + + final clientReport = _options.recorder.flush(); + filteredEnvelope.addClientReport(clientReport); + + if (filteredEnvelope.items.isNotEmpty) { + return _transport.send(filteredEnvelope); + } else { + return SentryId.empty(); + } + } +} diff --git a/packages/dart/lib/src/transport/data_category.dart b/packages/dart/lib/src/transport/data_category.dart new file mode 100644 index 0000000000..89f983f3a7 --- /dev/null +++ b/packages/dart/lib/src/transport/data_category.dart @@ -0,0 +1,38 @@ +/// Different category types of data sent to Sentry. Used for rate limiting and client reports. +enum DataCategory { + all, + dataCategoryDefault, // default + error, + session, + transaction, + span, + attachment, + security, + metricBucket, + logItem, + feedback, + unknown; + + static DataCategory fromItemType(String itemType) { + switch (itemType) { + case 'event': + return DataCategory.error; + case 'session': + return DataCategory.session; + case 'attachment': + return DataCategory.attachment; + case 'transaction': + return DataCategory.transaction; + // The envelope item type used for metrics is statsd, + // whereas the client report category is metric_bucket + case 'statsd': + return DataCategory.metricBucket; + case 'log': + return DataCategory.logItem; + case 'feedback': + return DataCategory.feedback; + default: + return DataCategory.unknown; + } + } +} diff --git a/packages/dart/lib/src/transport/encode.dart b/packages/dart/lib/src/transport/encode.dart new file mode 100644 index 0000000000..645aaf02cd --- /dev/null +++ b/packages/dart/lib/src/transport/encode.dart @@ -0,0 +1,14 @@ +import 'dart:io'; + +/// Encodes the body using Gzip compression +List compressBody(List body, Map headers) { + headers['Content-Encoding'] = 'gzip'; + return gzip.encode(body); +} + +/// Encodes bytes in sink using Gzip compression +Sink> compressInSink( + Sink> sink, Map headers) { + headers['Content-Encoding'] = 'gzip'; + return GZipCodec().encoder.startChunkedConversion(sink); +} diff --git a/packages/dart/lib/src/transport/http_transport.dart b/packages/dart/lib/src/transport/http_transport.dart new file mode 100644 index 0000000000..73c8e41c69 --- /dev/null +++ b/packages/dart/lib/src/transport/http_transport.dart @@ -0,0 +1,87 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:http/http.dart'; + +import '../http_client/client_provider.dart' + if (dart.library.io) '../http_client/io_client_provider.dart'; +import '../noop_client.dart'; +import '../protocol.dart'; +import '../sentry_envelope.dart'; +import '../sentry_options.dart'; +import '../utils/transport_utils.dart'; +import 'http_transport_request_handler.dart'; +import 'rate_limiter.dart'; +import 'transport.dart'; + +/// A transport is in charge of sending the event to the Sentry server. +class HttpTransport implements Transport { + final SentryOptions _options; + + final RateLimiter _rateLimiter; + + final HttpTransportRequestHandler _requestHandler; + + factory HttpTransport(SentryOptions options, RateLimiter rateLimiter) { + if (options.httpClient is NoOpClient) { + options.httpClient = getClientProvider().getClient(options); + } + return HttpTransport._(options, rateLimiter); + } + + HttpTransport._(this._options, this._rateLimiter) + : _requestHandler = + HttpTransportRequestHandler(_options, _options.parsedDsn.postUri); + + @override + Future send(SentryEnvelope envelope) async { + envelope.header.sentAt = _options.clock(); + + final streamedRequest = await _requestHandler.createRequest(envelope); + + final response = await _options.httpClient + .send(streamedRequest) + .then(Response.fromStream); + + _updateRetryAfterLimits(response); + + TransportUtils.logResponse(_options, envelope, response, target: 'Sentry'); + + if (response.statusCode == 200) { + return _parseEventId(response); + } + if (response.statusCode == 429) { + _options.log( + SentryLevel.warning, 'Rate limit reached, failed to send envelope'); + } + return SentryId.empty(); + } + + SentryId? _parseEventId(Response response) { + try { + final eventId = json.decode(response.body)['id']; + return eventId != null ? SentryId.fromId(eventId) : null; + } catch (e) { + _options.log(SentryLevel.error, 'Error parsing response: $e'); + if (_options.automatedTestMode) { + rethrow; + } + return null; + } + } + + void _updateRetryAfterLimits(Response response) { + // seconds + final retryAfterHeader = response.headers['Retry-After']; + + // X-Sentry-Rate-Limits looks like: seconds:categories:scope + // it could have more than one scope so it looks like: + // quota_limit, quota_limit, quota_limit + + // a real example: 50:transaction:key, 2700:default;error;security:organization + // 50::key is also a valid case, it means no categories and it should apply to all of them + final sentryRateLimitHeader = response.headers['X-Sentry-Rate-Limits']; + _rateLimiter.updateRetryAfterLimits( + sentryRateLimitHeader, retryAfterHeader, response.statusCode); + } +} diff --git a/packages/dart/lib/src/transport/http_transport_request_handler.dart b/packages/dart/lib/src/transport/http_transport_request_handler.dart new file mode 100644 index 0000000000..9d1bdd44f4 --- /dev/null +++ b/packages/dart/lib/src/transport/http_transport_request_handler.dart @@ -0,0 +1,98 @@ +import 'dart:async'; + +import 'package:http/http.dart'; +import 'package:meta/meta.dart'; + +import 'noop_encode.dart' if (dart.library.io) 'encode.dart'; +import '../protocol.dart'; +import '../sentry_options.dart'; +import '../sentry_envelope.dart'; + +@internal +class HttpTransportRequestHandler { + final SentryOptions _options; + final Dsn _dsn; + final Map _headers; + final Uri _requestUri; + late _CredentialBuilder _credentialBuilder; + + HttpTransportRequestHandler(this._options, this._requestUri) + : _dsn = _options.parsedDsn, + _headers = _buildHeaders( + _options.platform.isWeb, + _options.sentryClientName, + ) { + _credentialBuilder = _CredentialBuilder( + _dsn, + _options.sentryClientName, + ); + } + + Future createRequest(SentryEnvelope envelope) async { + final streamedRequest = StreamedRequest('POST', _requestUri); + + if (_options.compressPayload) { + final compressionSink = compressInSink(streamedRequest.sink, _headers); + envelope + .envelopeStream(_options) + .listen(compressionSink.add) + .onDone(compressionSink.close); + } else { + envelope + .envelopeStream(_options) + .listen(streamedRequest.sink.add) + .onDone(streamedRequest.sink.close); + } + + streamedRequest.headers.addAll(_credentialBuilder.configure(_headers)); + return streamedRequest; + } +} + +Map _buildHeaders(bool isWeb, String sdkIdentifier) { + final headers = {'Content-Type': 'application/x-sentry-envelope'}; + // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why + // for web it use browser user agent + if (!isWeb) { + headers['User-Agent'] = sdkIdentifier; + } + return headers; +} + +class _CredentialBuilder { + final String _authHeader; + + _CredentialBuilder._(String authHeader) : _authHeader = authHeader; + + factory _CredentialBuilder(Dsn dsn, String sdkIdentifier) { + final authHeader = _buildAuthHeader( + publicKey: dsn.publicKey, + secretKey: dsn.secretKey, + sdkIdentifier: sdkIdentifier, + ); + + return _CredentialBuilder._(authHeader); + } + + static String _buildAuthHeader({ + required String publicKey, + String? secretKey, + required String sdkIdentifier, + }) { + var header = 'Sentry sentry_version=7, sentry_client=$sdkIdentifier, ' + 'sentry_key=$publicKey'; + + if (secretKey != null) { + header += ', sentry_secret=$secretKey'; + } + + return header; + } + + Map configure(Map headers) { + return headers + ..addAll( + {'X-Sentry-Auth': _authHeader}, + ); + } +} diff --git a/packages/dart/lib/src/transport/noop_encode.dart b/packages/dart/lib/src/transport/noop_encode.dart new file mode 100644 index 0000000000..fa1f9c5547 --- /dev/null +++ b/packages/dart/lib/src/transport/noop_encode.dart @@ -0,0 +1,7 @@ +/// gzip compression is not available on browser +List compressBody(List body, Map headers) => body; + +/// gzip compression is not available on browser +Sink> compressInSink( + Sink> sink, Map headers) => + sink; diff --git a/packages/dart/lib/src/transport/noop_transport.dart b/packages/dart/lib/src/transport/noop_transport.dart new file mode 100644 index 0000000000..f4ae138e99 --- /dev/null +++ b/packages/dart/lib/src/transport/noop_transport.dart @@ -0,0 +1,11 @@ +import 'dart:async'; + +import '../sentry_envelope.dart'; + +import '../protocol.dart'; +import 'transport.dart'; + +class NoOpTransport implements Transport { + @override + Future send(SentryEnvelope envelope) async => null; +} diff --git a/packages/dart/lib/src/transport/rate_limit.dart b/packages/dart/lib/src/transport/rate_limit.dart new file mode 100644 index 0000000000..00284a3ba7 --- /dev/null +++ b/packages/dart/lib/src/transport/rate_limit.dart @@ -0,0 +1,11 @@ +import 'data_category.dart'; + +/// `RateLimit` containing limited `DataCategory` and duration in milliseconds. +class RateLimit { + RateLimit(this.category, this.duration, {List? namespaces}) + : namespaces = (namespaces?..removeWhere((e) => e.isEmpty)) ?? []; + + final DataCategory category; + final Duration duration; + final List namespaces; +} diff --git a/packages/dart/lib/src/transport/rate_limit_parser.dart b/packages/dart/lib/src/transport/rate_limit_parser.dart new file mode 100644 index 0000000000..f0fea1dfde --- /dev/null +++ b/packages/dart/lib/src/transport/rate_limit_parser.dart @@ -0,0 +1,95 @@ +import 'data_category.dart'; +import 'rate_limit.dart'; + +/// Parse rate limit categories and times from response header payloads. +class RateLimitParser { + RateLimitParser(this._header); + + static const httpRetryAfterDefaultDelay = Duration(milliseconds: 60000); + + final String? _header; + + List parseRateLimitHeader() { + final rateLimitHeader = _header; + if (rateLimitHeader == null) { + return []; + } + // example: 2700:metric_bucket:organization:quota_exceeded:custom,... + final rateLimits = []; + final rateLimitValues = rateLimitHeader.toLowerCase().split(','); + for (final rateLimitValue in rateLimitValues) { + final durationAndCategories = rateLimitValue.trim().split(':'); + if (durationAndCategories.isEmpty) { + continue; + } + final duration = _parseRetryAfterOrDefault(durationAndCategories[0]); + if (durationAndCategories.length <= 1) { + continue; + } + final allCategories = durationAndCategories[1]; + if (allCategories.isNotEmpty) { + final categoryValues = allCategories.split(';'); + for (final categoryValue in categoryValues) { + final category = _DataCategoryExtension._fromStringValue( + categoryValue); // Metric buckets rate limit can have namespaces + if (category == DataCategory.metricBucket) { + final namespaces = durationAndCategories.length > 4 + ? durationAndCategories[4] + : null; + rateLimits.add(RateLimit( + category, + duration, + namespaces: namespaces?.trim().split(','), + )); + } else if (category != DataCategory.unknown) { + rateLimits.add(RateLimit(category, duration)); + } + } + } else { + rateLimits.add(RateLimit(DataCategory.all, duration)); + } + } + return rateLimits; + } + + List parseRetryAfterHeader() { + return [RateLimit(DataCategory.all, _parseRetryAfterOrDefault(_header))]; + } + + // Helper + + static Duration _parseRetryAfterOrDefault(String? value) { + final durationInSeconds = int.tryParse(value ?? ''); + if (durationInSeconds != null) { + return Duration(seconds: durationInSeconds); + } else { + return RateLimitParser.httpRetryAfterDefaultDelay; + } + } +} + +extension _DataCategoryExtension on DataCategory { + static DataCategory _fromStringValue(String stringValue) { + switch (stringValue) { + case '__all__': + return DataCategory.all; + case 'default': + return DataCategory.dataCategoryDefault; + case 'error': + return DataCategory.error; + case 'session': + return DataCategory.session; + case 'transaction': + return DataCategory.transaction; + case 'attachment': + return DataCategory.attachment; + case 'security': + return DataCategory.security; + case 'metric_bucket': + return DataCategory.metricBucket; + case 'log_item': + return DataCategory.logItem; + } + return DataCategory.unknown; + } +} diff --git a/packages/dart/lib/src/transport/rate_limiter.dart b/packages/dart/lib/src/transport/rate_limiter.dart new file mode 100644 index 0000000000..fa0f7a9018 --- /dev/null +++ b/packages/dart/lib/src/transport/rate_limiter.dart @@ -0,0 +1,124 @@ +import '../../sentry.dart'; +import '../transport/rate_limit_parser.dart'; +import 'rate_limit.dart'; +import 'data_category.dart'; +import '../client_reports/discard_reason.dart'; + +/// Controls retry limits on different category types sent to Sentry. +class RateLimiter { + RateLimiter(this._options); + + final SentryOptions _options; + final _rateLimitedUntil = {}; + + /// Filter out envelopes that are rate limited. + SentryEnvelope? filter(SentryEnvelope envelope) { + // Optimize for/No allocations if no items are under 429 + List? dropItems; + for (final item in envelope.items) { + // using the raw value of the enum to not expose SentryEnvelopeItemType + if (_isRetryAfter(item.header.type)) { + dropItems ??= []; + dropItems.add(item); + + _options.recorder.recordLostEvent( + DiscardReason.rateLimitBackoff, + DataCategory.fromItemType(item.header.type), + ); + + final originalObject = item.originalObject; + if (originalObject is SentryTransaction) { + _options.recorder.recordLostEvent( + DiscardReason.rateLimitBackoff, + DataCategory.span, + count: originalObject.spans.length + 1, + ); + } + } + } + + if (dropItems != null) { + // Need a new envelope + final toSend = []; + for (final item in envelope.items) { + if (!dropItems.contains(item)) { + toSend.add(item); + } + } + + // no reason to continue + if (toSend.isEmpty) { + return null; + } + + return SentryEnvelope(envelope.header, toSend); + } else { + return envelope; + } + } + + /// Update rate limited categories + void updateRetryAfterLimits( + String? sentryRateLimitHeader, String? retryAfterHeader, int errorCode) { + final currentDateTime = _options.clock().millisecondsSinceEpoch; + var rateLimits = []; + + if (sentryRateLimitHeader != null) { + rateLimits = + RateLimitParser(sentryRateLimitHeader).parseRateLimitHeader(); + } else if (errorCode == 429) { + rateLimits = RateLimitParser(retryAfterHeader).parseRetryAfterHeader(); + } + + for (final rateLimit in rateLimits) { + if (rateLimit.category == DataCategory.metricBucket && + rateLimit.namespaces.isNotEmpty && + !rateLimit.namespaces.contains('custom')) { + continue; + } + _applyRetryAfterOnlyIfLonger( + rateLimit.category, + DateTime.fromMillisecondsSinceEpoch( + currentDateTime + rateLimit.duration.inMilliseconds), + ); + } + } + + // Private + + bool _isRetryAfter(String itemType) { + final dataCategory = DataCategory.fromItemType(itemType); + final currentDate = DateTime.fromMillisecondsSinceEpoch( + _options.clock().millisecondsSinceEpoch); + + // check all categories + final dateAllCategories = _rateLimitedUntil[DataCategory.all]; + if (dateAllCategories != null) { + if (!currentDate.isAfter(dateAllCategories)) { + return true; + } + } + + // Unknown should not be rate limited + if (DataCategory.unknown == dataCategory) { + return false; + } + + // check for specific dataCategory + final dateCategory = _rateLimitedUntil[dataCategory]; + if (dateCategory != null) { + return !currentDate.isAfter(dateCategory); + } + + return false; + } + + void _applyRetryAfterOnlyIfLonger(DataCategory dataCategory, DateTime date) { + final oldDate = _rateLimitedUntil[dataCategory]; + + // only overwrite its previous date if the limit is even longer + if (oldDate == null || date.isAfter(oldDate)) { + _rateLimitedUntil[dataCategory] = date; + } + } +} diff --git a/packages/dart/lib/src/transport/spotlight_http_transport.dart b/packages/dart/lib/src/transport/spotlight_http_transport.dart new file mode 100644 index 0000000000..e2c98fda2a --- /dev/null +++ b/packages/dart/lib/src/transport/spotlight_http_transport.dart @@ -0,0 +1,59 @@ +import 'package:http/http.dart'; +import '../utils/transport_utils.dart'; +import 'http_transport_request_handler.dart'; + +import '../../sentry.dart'; +import '../noop_client.dart'; +import '../http_client/client_provider.dart' + if (dart.library.io) '../http_client/io_client_provider.dart'; + +/// Spotlight HTTP transport decorator that sends Sentry envelopes to both Sentry and Spotlight. +/// This will be used on platforms that do not have native SDK support. +/// Platforms with native SDK support will configure spotlight directly in the native SDK options. +class SpotlightHttpTransport extends Transport { + final SentryOptions _options; + final Transport _transport; + final HttpTransportRequestHandler _requestHandler; + + factory SpotlightHttpTransport(SentryOptions options, Transport transport) { + if (options.httpClient is NoOpClient) { + options.httpClient = getClientProvider().getClient(options); + } + return SpotlightHttpTransport._(options, transport); + } + + SpotlightHttpTransport._(this._options, this._transport) + : _requestHandler = HttpTransportRequestHandler(_options, + Uri.parse(_options.spotlight.url ?? _defaultSpotlightUrl())); + + @override + Future send(SentryEnvelope envelope) async { + try { + await _sendToSpotlight(envelope); + } catch (e) { + _options.log( + SentryLevel.warning, 'Failed to send envelope to Spotlight: $e'); + if (_options.automatedTestMode) { + rethrow; + } + } + return _transport.send(envelope); + } + + Future _sendToSpotlight(SentryEnvelope envelope) async { + envelope.header.sentAt = _options.clock(); + + final spotlightRequest = await _requestHandler.createRequest(envelope); + + final response = await _options.httpClient + .send(spotlightRequest) + .then(Response.fromStream); + + TransportUtils.logResponse(_options, envelope, response, + target: 'Spotlight'); + } +} + +String _defaultSpotlightUrl() { + return 'http://localhost:8969/stream'; +} diff --git a/packages/dart/lib/src/transport/task_queue.dart b/packages/dart/lib/src/transport/task_queue.dart new file mode 100644 index 0000000000..4c99393caa --- /dev/null +++ b/packages/dart/lib/src/transport/task_queue.dart @@ -0,0 +1,61 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; + +import '../../sentry.dart'; +import '../client_reports/client_report_recorder.dart'; +import '../client_reports/discard_reason.dart'; +import 'data_category.dart'; + +typedef Task = Future Function(); + +@internal +abstract class TaskQueue { + Future enqueue(Task task, T fallbackResult, DataCategory category); +} + +@internal +class DefaultTaskQueue implements TaskQueue { + DefaultTaskQueue(this._maxQueueSize, this._logger, this._recorder); + + final int _maxQueueSize; + final SdkLogCallback _logger; + final ClientReportRecorder _recorder; + + int _queueCount = 0; + + @override + Future enqueue( + Task task, + T fallbackResult, + DataCategory category, + ) async { + if (_queueCount >= _maxQueueSize) { + _recorder.recordLostEvent(DiscardReason.queueOverflow, category); + _logger( + SentryLevel.warning, + 'Task dropped due to reaching max ($_maxQueueSize} parallel tasks.).', + ); + return fallbackResult; + } else { + _queueCount++; + try { + return await task(); + } finally { + _queueCount--; + } + } + } +} + +@internal +class NoOpTaskQueue implements TaskQueue { + @override + Future enqueue( + Task task, + T fallbackResult, + DataCategory category, + ) { + return task(); + } +} diff --git a/packages/dart/lib/src/transport/transport.dart b/packages/dart/lib/src/transport/transport.dart new file mode 100644 index 0000000000..f0a6a2c996 --- /dev/null +++ b/packages/dart/lib/src/transport/transport.dart @@ -0,0 +1,10 @@ +import 'dart:async'; + +import '../sentry_envelope.dart'; +import '../protocol.dart'; + +/// A transport is in charge of sending the event/envelope either via http +/// or caching in the disk. +abstract class Transport { + Future send(SentryEnvelope envelope); +} diff --git a/packages/dart/lib/src/type_check_hint.dart b/packages/dart/lib/src/type_check_hint.dart new file mode 100644 index 0000000000..7e47c1ecbf --- /dev/null +++ b/packages/dart/lib/src/type_check_hint.dart @@ -0,0 +1,26 @@ +import 'package:meta/meta.dart'; +import 'http_client/failed_request_client.dart'; + +/// Constants used for Type Check hints. +class TypeCheckHint { + /// Used for Synthetic exceptions. + static const syntheticException = 'syntheticException'; + + /// Used for [FailedRequestClient] for request hint + static const httpRequest = 'request'; + + /// Used for [FailedRequestClient] for response hint + static const httpResponse = 'response'; + + /// Used for `sentry_logging/LoggingIntegration` for `sentry_logging/LogRecord` hint + static const record = 'record'; + + /// Widget that was tapped in `sentry_flutter/SentryUserInteractionWidget` + static const widget = 'widget'; + + /// Used to indicate that the SDK added a synthetic current stack trace. + static const currentStackTrace = 'currentStackTrace'; + + @internal + static const isWidgetFeedback = 'isWidgetFeedback'; +} diff --git a/packages/dart/lib/src/utils.dart b/packages/dart/lib/src/utils.dart new file mode 100644 index 0000000000..db5ca614c2 --- /dev/null +++ b/packages/dart/lib/src/utils.dart @@ -0,0 +1,36 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:convert'; + +import 'package:meta/meta.dart'; + +/// Sentry does not take a timezone and instead expects the date-time to be +/// submitted in UTC timezone. +@internal +DateTime getUtcDateTime() => DateTime.now().toUtc(); + +/// Formats a Date as ISO8601 and UTC with millis precision +@internal +String formatDateAsIso8601WithMillisPrecision(DateTime date) { + var iso = date.toIso8601String(); + final millisecondSeparatorIndex = iso.lastIndexOf('.'); + if (millisecondSeparatorIndex != -1) { + // + 4 for millis precision + iso = iso.substring(0, millisecondSeparatorIndex + 4); + } + // appends Z because the substring removed it + return '${iso}Z'; +} + +@internal +final utf8JsonEncoder = JsonUtf8Encoder(null, jsonSerializationFallback, null); + +@internal +Object? jsonSerializationFallback(Object? nonEncodable) { + if (nonEncodable == null) { + return null; + } + return nonEncodable.toString(); +} diff --git a/packages/dart/lib/src/utils/_io_get_isolate_name.dart b/packages/dart/lib/src/utils/_io_get_isolate_name.dart new file mode 100644 index 0000000000..93a7c993d0 --- /dev/null +++ b/packages/dart/lib/src/utils/_io_get_isolate_name.dart @@ -0,0 +1,3 @@ +import 'dart:isolate'; + +String? getIsolateName() => Isolate.current.debugName; diff --git a/packages/dart/lib/src/utils/_io_get_sentry_operating_system.dart b/packages/dart/lib/src/utils/_io_get_sentry_operating_system.dart new file mode 100644 index 0000000000..e825ccfd6e --- /dev/null +++ b/packages/dart/lib/src/utils/_io_get_sentry_operating_system.dart @@ -0,0 +1,72 @@ +import '../protocol/sentry_operating_system.dart'; +import 'dart:io'; +import 'package:meta/meta.dart'; + +@internal +SentryOperatingSystem getSentryOperatingSystem({ + String? name, + String? rawDescription, +}) { + name ??= Platform.operatingSystem; + rawDescription ??= Platform.operatingSystemVersion; + RegExpMatch? match; + switch (name) { + case 'android': + match = _androidOsRegexp.firstMatch(rawDescription); + name = 'Android'; + break; + case 'ios': + name = 'iOS'; + match = _appleOsRegexp.firstMatch(rawDescription); + break; + case 'macos': + name = 'macOS'; + match = _appleOsRegexp.firstMatch(rawDescription); + break; + case 'linux': + name = 'Linux'; + match = _linuxOsRegexp.firstMatch(rawDescription); + break; + case 'windows': + name = 'Windows'; + match = _windowsOsRegexp.firstMatch(rawDescription); + break; + } + + return SentryOperatingSystem( + name: name, + rawDescription: rawDescription, + version: match?.namedGroupOrNull('version'), + build: match?.namedGroupOrNull('build'), + kernelVersion: match?.namedGroupOrNull('kernelVersion'), + ); +} + +// LYA-L29 10.1.0.289(C432E7R1P5) +// TE1A.220922.010 +final _androidOsRegexp = RegExp('^(?.*)\$', caseSensitive: false); + +// Linux 5.11.0-1018-gcp #20~20.04.2-Ubuntu SMP Fri Sep 3 01:01:37 UTC 2021 +final _linuxOsRegexp = RegExp( + '(?[a-z0-9+.\\-]+) (?#.*)\$', + caseSensitive: false); + +// Version 14.5 (Build 18E182) +final _appleOsRegexp = RegExp( + '(?[a-z0-9+.\\-]+)( \\(Build (?[a-z0-9+.\\-]+))\\)?\$', + caseSensitive: false); + +// "Windows 10 Pro" 10.0 (Build 19043) +final _windowsOsRegexp = RegExp( + ' (?[a-z0-9+.\\-]+)( \\(Build (?[a-z0-9+.\\-]+))\\)?\$', + caseSensitive: false); + +extension on RegExpMatch { + String? namedGroupOrNull(String name) { + if (groupNames.contains(name)) { + return namedGroup(name); + } else { + return null; + } + } +} diff --git a/packages/dart/lib/src/utils/_web_get_isolate_name.dart b/packages/dart/lib/src/utils/_web_get_isolate_name.dart new file mode 100644 index 0000000000..0db3f82b99 --- /dev/null +++ b/packages/dart/lib/src/utils/_web_get_isolate_name.dart @@ -0,0 +1 @@ +String? getIsolateName() => null; diff --git a/packages/dart/lib/src/utils/_web_get_sentry_operating_system.dart b/packages/dart/lib/src/utils/_web_get_sentry_operating_system.dart new file mode 100644 index 0000000000..1ec1309398 --- /dev/null +++ b/packages/dart/lib/src/utils/_web_get_sentry_operating_system.dart @@ -0,0 +1,13 @@ +import '../protocol/sentry_operating_system.dart'; +import 'package:meta/meta.dart'; + +@internal +SentryOperatingSystem getSentryOperatingSystem({ + String? name, + String? rawDescription, +}) { + final os = SentryOperatingSystem(); + os.name = name ?? os.name; + os.rawDescription = rawDescription ?? os.rawDescription; + return os; +} diff --git a/packages/dart/lib/src/utils/breadcrumb_log_level.dart b/packages/dart/lib/src/utils/breadcrumb_log_level.dart new file mode 100644 index 0000000000..71eb86ec47 --- /dev/null +++ b/packages/dart/lib/src/utils/breadcrumb_log_level.dart @@ -0,0 +1,16 @@ +import 'package:meta/meta.dart'; + +import '../../sentry.dart'; + +/// Determine a breadcrumb's log level (only `warning` or `error`) based on an HTTP status code. +@internal +SentryLevel? getBreadcrumbLogLevelFromHttpStatusCode(int statusCode) { + // NOTE: null defaults to 'info' in Sentry + if (statusCode >= 400 && statusCode < 500) { + return SentryLevel.warning; + } else if (statusCode >= 500 && statusCode < 600) { + return SentryLevel.error; + } else { + return null; + } +} diff --git a/packages/dart/lib/src/utils/crc32_utils.dart b/packages/dart/lib/src/utils/crc32_utils.dart new file mode 100644 index 0000000000..8e6f63fd53 --- /dev/null +++ b/packages/dart/lib/src/utils/crc32_utils.dart @@ -0,0 +1,313 @@ +// Adapted from the archive library (https://pub.dev/packages/archive) +// https://github.com/brendan-duncan/archive/blob/21c864efe0df2b7fd962b59ff0a714c96732bf7d/lib/src/util/crc32.dart +// +// The MIT License +// +// Copyright (c) 2013-2021 Brendan Duncan. +// All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +/// Util class to compute the CRC-32 checksum of a given array. +class Crc32Utils { + /// Get the CRC-32 checksum of the given array. You can append bytes to an + /// already computed crc by specifying the previous [crc] value. + static int getCrc32(List array, [int crc = 0]) { + var len = array.length; + crc = crc ^ 0xffffffff; + var ip = 0; + while (len >= 8) { + crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); + crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); + crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); + crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); + crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); + crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); + crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); + crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); + len -= 8; + } + if (len > 0) { + do { + crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); + } while (--len > 0); + } + return crc ^ 0xffffffff; + } +} + +// Precomputed CRC table for faster calculations. +const List _crc32Table = [ + 0, + 1996959894, + 3993919788, + 2567524794, + 124634137, + 1886057615, + 3915621685, + 2657392035, + 249268274, + 2044508324, + 3772115230, + 2547177864, + 162941995, + 2125561021, + 3887607047, + 2428444049, + 498536548, + 1789927666, + 4089016648, + 2227061214, + 450548861, + 1843258603, + 4107580753, + 2211677639, + 325883990, + 1684777152, + 4251122042, + 2321926636, + 335633487, + 1661365465, + 4195302755, + 2366115317, + 997073096, + 1281953886, + 3579855332, + 2724688242, + 1006888145, + 1258607687, + 3524101629, + 2768942443, + 901097722, + 1119000684, + 3686517206, + 2898065728, + 853044451, + 1172266101, + 3705015759, + 2882616665, + 651767980, + 1373503546, + 3369554304, + 3218104598, + 565507253, + 1454621731, + 3485111705, + 3099436303, + 671266974, + 1594198024, + 3322730930, + 2970347812, + 795835527, + 1483230225, + 3244367275, + 3060149565, + 1994146192, + 31158534, + 2563907772, + 4023717930, + 1907459465, + 112637215, + 2680153253, + 3904427059, + 2013776290, + 251722036, + 2517215374, + 3775830040, + 2137656763, + 141376813, + 2439277719, + 3865271297, + 1802195444, + 476864866, + 2238001368, + 4066508878, + 1812370925, + 453092731, + 2181625025, + 4111451223, + 1706088902, + 314042704, + 2344532202, + 4240017532, + 1658658271, + 366619977, + 2362670323, + 4224994405, + 1303535960, + 984961486, + 2747007092, + 3569037538, + 1256170817, + 1037604311, + 2765210733, + 3554079995, + 1131014506, + 879679996, + 2909243462, + 3663771856, + 1141124467, + 855842277, + 2852801631, + 3708648649, + 1342533948, + 654459306, + 3188396048, + 3373015174, + 1466479909, + 544179635, + 3110523913, + 3462522015, + 1591671054, + 702138776, + 2966460450, + 3352799412, + 1504918807, + 783551873, + 3082640443, + 3233442989, + 3988292384, + 2596254646, + 62317068, + 1957810842, + 3939845945, + 2647816111, + 81470997, + 1943803523, + 3814918930, + 2489596804, + 225274430, + 2053790376, + 3826175755, + 2466906013, + 167816743, + 2097651377, + 4027552580, + 2265490386, + 503444072, + 1762050814, + 4150417245, + 2154129355, + 426522225, + 1852507879, + 4275313526, + 2312317920, + 282753626, + 1742555852, + 4189708143, + 2394877945, + 397917763, + 1622183637, + 3604390888, + 2714866558, + 953729732, + 1340076626, + 3518719985, + 2797360999, + 1068828381, + 1219638859, + 3624741850, + 2936675148, + 906185462, + 1090812512, + 3747672003, + 2825379669, + 829329135, + 1181335161, + 3412177804, + 3160834842, + 628085408, + 1382605366, + 3423369109, + 3138078467, + 570562233, + 1426400815, + 3317316542, + 2998733608, + 733239954, + 1555261956, + 3268935591, + 3050360625, + 752459403, + 1541320221, + 2607071920, + 3965973030, + 1969922972, + 40735498, + 2617837225, + 3943577151, + 1913087877, + 83908371, + 2512341634, + 3803740692, + 2075208622, + 213261112, + 2463272603, + 3855990285, + 2094854071, + 198958881, + 2262029012, + 4057260610, + 1759359992, + 534414190, + 2176718541, + 4139329115, + 1873836001, + 414664567, + 2282248934, + 4279200368, + 1711684554, + 285281116, + 2405801727, + 4167216745, + 1634467795, + 376229701, + 2685067896, + 3608007406, + 1308918612, + 956543938, + 2808555105, + 3495958263, + 1231636301, + 1047427035, + 2932959818, + 3654703836, + 1088359270, + 936918000, + 2847714899, + 3736837829, + 1202900863, + 817233897, + 3183342108, + 3401237130, + 1404277552, + 615818150, + 3134207493, + 3453421203, + 1423857449, + 601450431, + 3009837614, + 3294710456, + 1567103746, + 711928724, + 3020668471, + 3272380065, + 1510334235, + 755167117 +]; diff --git a/packages/dart/lib/src/utils/http_header_utils.dart b/packages/dart/lib/src/utils/http_header_utils.dart new file mode 100644 index 0000000000..a055f47a78 --- /dev/null +++ b/packages/dart/lib/src/utils/http_header_utils.dart @@ -0,0 +1,16 @@ +import 'package:meta/meta.dart'; + +/// Helper to extract header data +@internal +class HttpHeaderUtils { + /// Get `Content-Length` header + static int? getContentLength(Map> headers) { + final contentLengthHeader = + headers['content-length'] ?? headers['Content-Length']; + if (contentLengthHeader != null && contentLengthHeader.isNotEmpty) { + final headerValue = contentLengthHeader.first; + return int.tryParse(headerValue); + } + return null; + } +} diff --git a/packages/dart/lib/src/utils/http_sanitizer.dart b/packages/dart/lib/src/utils/http_sanitizer.dart new file mode 100644 index 0000000000..8d0fbf6215 --- /dev/null +++ b/packages/dart/lib/src/utils/http_sanitizer.dart @@ -0,0 +1,91 @@ +import 'package:meta/meta.dart'; + +import '../protocol.dart'; +import 'url_details.dart'; + +@internal +class HttpSanitizer { + static final List _securityHeaders = [ + "X-FORWARDED-FOR", + "AUTHORIZATION", + "COOKIE", + "SET-COOKIE", + "X-API-KEY", + "X-REAL-IP", + "REMOTE-ADDR", + "FORWARDED", + "PROXY-AUTHORIZATION", + "X-CSRF-TOKEN", + "X-CSRFTOKEN", + "X-XSRF-TOKEN" + ]; + + /// Parse and sanitize url data for sentry.io + static UrlDetails? sanitizeUrl(String? url) { + if (url == null) { + return null; + } + + final queryIndex = url.indexOf('?'); + final fragmentIndex = url.indexOf('#'); + + if (queryIndex > -1 && fragmentIndex > -1 && fragmentIndex < queryIndex) { + // url considered malformed because of fragment position + return UrlDetails(); + } else { + try { + final uri = Uri.parse(url); + final urlWithRedactedAuth = uri._urlWithRedactedAuth(); + return UrlDetails( + url: urlWithRedactedAuth.isEmpty ? null : urlWithRedactedAuth, + query: uri.query.isEmpty ? null : uri.query, + fragment: uri.fragment.isEmpty ? null : uri.fragment); + } catch (_) { + return null; + } + } + } + + static Map? sanitizedHeaders(Map? headers) { + if (headers == null) { + return null; + } + final sanitizedHeaders = {}; + headers.forEach((key, value) { + if (!_securityHeaders.contains(key.toUpperCase())) { + sanitizedHeaders[key] = value; + } + }); + return sanitizedHeaders; + } +} + +extension _UriPath on Uri { + String _urlWithRedactedAuth() { + var buffer = ''; + if (scheme.isNotEmpty) { + buffer += '$scheme://'; + } + if (userInfo.isNotEmpty) { + buffer += + userInfo.contains(":") ? "[Filtered]:[Filtered]@" : "[Filtered]@"; + } + buffer += host; + if (path.isNotEmpty) { + buffer += path; + } + return buffer; + } +} + +@internal +extension SanitizedSentryRequest on SentryRequest { + void sanitize() { + final urlDetails = HttpSanitizer.sanitizeUrl(url) ?? UrlDetails(); + url = urlDetails.urlOrFallback; + queryString = urlDetails.query; + fragment = urlDetails.fragment; + headers = HttpSanitizer.sanitizedHeaders(headers) ?? {}; + cookies = null; + } +} diff --git a/packages/dart/lib/src/utils/isolate_utils.dart b/packages/dart/lib/src/utils/isolate_utils.dart new file mode 100644 index 0000000000..0db3332677 --- /dev/null +++ b/packages/dart/lib/src/utils/isolate_utils.dart @@ -0,0 +1,7 @@ +import 'package:meta/meta.dart'; + +import '_io_get_isolate_name.dart' + if (dart.library.js_interop) '_web_get_isolate_name.dart' as isolate_getter; + +@internal +String? getIsolateName() => isolate_getter.getIsolateName(); diff --git a/packages/dart/lib/src/utils/iterable_utils.dart b/packages/dart/lib/src/utils/iterable_utils.dart new file mode 100644 index 0000000000..c2fb5e5c69 --- /dev/null +++ b/packages/dart/lib/src/utils/iterable_utils.dart @@ -0,0 +1,17 @@ +import 'package:meta/meta.dart'; + +@internal +class IterableUtils { + static T? firstWhereOrNull( + Iterable? iterable, + bool Function(T item) test, + ) { + if (iterable == null) { + return null; + } + for (var item in iterable) { + if (test(item)) return item; + } + return null; + } +} diff --git a/packages/dart/lib/src/utils/os_utils.dart b/packages/dart/lib/src/utils/os_utils.dart new file mode 100644 index 0000000000..a1b0cdf8c2 --- /dev/null +++ b/packages/dart/lib/src/utils/os_utils.dart @@ -0,0 +1,14 @@ +import 'package:meta/meta.dart'; +import '../protocol/sentry_operating_system.dart'; + +import '_web_get_sentry_operating_system.dart' + if (dart.library.io) '_io_get_sentry_operating_system.dart' as os_getter; + +@internal +SentryOperatingSystem getSentryOperatingSystem({ + String? name, + String? rawDescription, +}) { + return os_getter.getSentryOperatingSystem( + name: name, rawDescription: rawDescription); +} diff --git a/packages/dart/lib/src/utils/regex_utils.dart b/packages/dart/lib/src/utils/regex_utils.dart new file mode 100644 index 0000000000..ba64f7504e --- /dev/null +++ b/packages/dart/lib/src/utils/regex_utils.dart @@ -0,0 +1,9 @@ +import 'package:meta/meta.dart'; + +@internal +bool isMatchingRegexPattern(String value, List regexPattern, + {bool caseSensitive = false}) { + final combinedRegexPattern = regexPattern.join('|'); + final regExp = RegExp(combinedRegexPattern, caseSensitive: caseSensitive); + return regExp.hasMatch(value); +} diff --git a/packages/dart/lib/src/utils/sample_rate_format.dart b/packages/dart/lib/src/utils/sample_rate_format.dart new file mode 100644 index 0000000000..6fb0bcb7e4 --- /dev/null +++ b/packages/dart/lib/src/utils/sample_rate_format.dart @@ -0,0 +1,324 @@ +// ignore: dangling_library_doc_comments +/// Code ported & adapted from `intl` package +/// https://pub.dev/packages/intl +/// +/// License: +/// +/// Copyright 2013, the Dart project authors. +/// +/// Redistribution and use in source and binary forms, with or without +/// modification, are permitted provided that the following conditions are +/// met: +/// +/// * Redistributions of source code must retain the above copyright +/// notice, this list of conditions and the following disclaimer. +/// * Redistributions in binary form must reproduce the above +/// copyright notice, this list of conditions and the following +/// disclaimer in the documentation and/or other materials provided +/// with the distribution. +/// * Neither the name of Google LLC nor the names of its +/// contributors may be used to endorse or promote products derived +/// from this software without specific prior written permission. +/// +/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +/// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +/// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +/// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +/// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +/// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +/// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +/// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +/// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +/// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +/// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import 'dart:math'; + +import 'package:meta/meta.dart'; + +@internal +class SampleRateFormat { + int _minimumIntegerDigits; + int _maximumFractionDigits; + int _minimumFractionDigits; + + /// The difference between our zero and '0'. + /// + /// In other words, a constant _localeZero - _zero. Initialized when + /// the locale is set. + final int _zeroOffset; + + /// Caches the symbols + final _NumberSymbols _symbols; + + /// Transient internal state in which to build up the result of the format + /// operation. We can have this be just an instance variable because Dart is + /// single-threaded and unless we do an asynchronous operation in the process + /// of formatting then there will only ever be one number being formatted + /// at a time. In languages with threads we'd need to pass this on the stack. + final StringBuffer _buffer = StringBuffer(); + + factory SampleRateFormat() { + var symbols = _NumberSymbols( + DECIMAL_SEP: '.', + ZERO_DIGIT: '0', + ); + var localeZero = symbols.ZERO_DIGIT.codeUnitAt(0); + var zeroOffset = localeZero - '0'.codeUnitAt(0); + + return SampleRateFormat._( + symbols, + zeroOffset, + ); + } + + SampleRateFormat._(this._symbols, this._zeroOffset) + : _minimumIntegerDigits = 1, + _maximumFractionDigits = 16, + _minimumFractionDigits = 0; + + /// Format the sample rate + String format(dynamic sampleRate) { + try { + if (_isNaN(sampleRate)) return '0'; + if (_isSmallerZero(sampleRate)) { + sampleRate = 0; + } + if (_isLargerOne(sampleRate)) { + sampleRate = 1; + } + _formatFixed(sampleRate.abs()); + + var result = _buffer.toString(); + _buffer.clear(); + return result; + } catch (_) { + _buffer.clear(); + return '0'; + } + } + + /// Used to test if we have exceeded integer limits. + static final _maxInt = 1 is double ? pow(2, 52) : 1.0e300.floor(); + static final _maxDigits = (log(_maxInt) / log(10)).ceil(); + + bool _isNaN(dynamic number) => number is num ? number.isNaN : false; + bool _isSmallerZero(dynamic number) => number is num ? number < 0 : false; + bool _isLargerOne(dynamic number) => number is num ? number > 1 : false; + + /// Format the basic number portion, including the fractional digits. + void _formatFixed(dynamic number) { + dynamic integerPart; + int fractionPart; + int extraIntegerDigits; + var fractionDigits = _maximumFractionDigits; + var minFractionDigits = _minimumFractionDigits; + + var power = 0; + int digitMultiplier; + + // We have three possible pieces. First, the basic integer part. If this + // is a percent or permille, the additional 2 or 3 digits. Finally the + // fractional part. + // We avoid multiplying the number because it might overflow if we have + // a fixed-size integer type, so we extract each of the three as an + // integer pieces. + integerPart = _floor(number); + var fraction = number - integerPart; + if (fraction.toInt() != 0) { + // If the fractional part leftover is > 1, presumbly the number + // was too big for a fixed-size integer, so leave it as whatever + // it was - the obvious thing is a double. + integerPart = number; + fraction = 0; + } + + power = pow(10, fractionDigits) as int; + digitMultiplier = power; + + // Multiply out to the number of decimal places and the percent, then + // round. For fixed-size integer types this should always be zero, so + // multiplying is OK. + var remainingDigits = _round(fraction * digitMultiplier).toInt(); + + if (remainingDigits >= digitMultiplier) { + // Overflow into the main digits: 0.99 => 1.00 + integerPart++; + remainingDigits -= digitMultiplier; + } else if (_numberOfIntegerDigits(remainingDigits) > + _numberOfIntegerDigits(_floor(fraction * digitMultiplier).toInt())) { + // Fraction has been rounded (0.0996 -> 0.1). + fraction = remainingDigits / digitMultiplier; + } + + // Separate out the extra integer parts from the fraction part. + extraIntegerDigits = remainingDigits ~/ power; + fractionPart = remainingDigits % power; + + var integerDigits = _integerDigits(integerPart, extraIntegerDigits); + var digitLength = integerDigits.length; + var fractionPresent = + fractionDigits > 0 && (minFractionDigits > 0 || fractionPart > 0); + + if (_hasIntegerDigits(integerDigits)) { + // Add the padding digits to the regular digits so that we get grouping. + var padding = '0' * (_minimumIntegerDigits - digitLength); + integerDigits = '$padding$integerDigits'; + digitLength = integerDigits.length; + for (var i = 0; i < digitLength; i++) { + _addDigit(integerDigits.codeUnitAt(i)); + } + } else if (!fractionPresent) { + // If neither fraction nor integer part exists, just print zero. + _addZero(); + } + + _decimalSeparator(fractionPresent); + if (fractionPresent) { + _formatFractionPart((fractionPart + power).toString(), minFractionDigits); + } + } + + /// Helper to get the floor of a number which might not be num. This should + /// only ever be called with an argument which is positive, or whose abs() + /// is negative. The second case is the maximum negative value on a + /// fixed-length integer. Since they are integers, they are also their own + /// floor. + dynamic _floor(dynamic number) { + if (number.isNegative && !number.abs().isNegative) { + throw ArgumentError( + 'Internal error: expected positive number, got $number'); + } + return (number is num) ? number.floor() : number ~/ 1; + } + + /// Helper to round a number which might not be num. + dynamic _round(dynamic number) { + if (number is num) { + if (number.isInfinite) { + return _maxInt; + } else { + return number.round(); + } + } else if (number.remainder(1) == 0) { + // Not a normal number, but int-like, e.g. Int64 + return number; + } else { + // TODO(alanknight): Do this more efficiently. If IntX had floor and + // round we could avoid this. + var basic = _floor(number); + var fraction = (number - basic).toDouble().round(); + return fraction == 0 ? number : number + fraction; + } + } + + // Return the number of digits left of the decimal place in [number]. + static int _numberOfIntegerDigits(dynamic number) { + var simpleNumber = (number.toDouble() as double).abs(); + // It's unfortunate that we have to do this, but we get precision errors + // that affect the result if we use logs, e.g. 1000000 + if (simpleNumber < 10) return 1; + if (simpleNumber < 100) return 2; + if (simpleNumber < 1000) return 3; + if (simpleNumber < 10000) return 4; + if (simpleNumber < 100000) return 5; + if (simpleNumber < 1000000) return 6; + if (simpleNumber < 10000000) return 7; + if (simpleNumber < 100000000) return 8; + if (simpleNumber < 1000000000) return 9; + if (simpleNumber < 10000000000) return 10; + if (simpleNumber < 100000000000) return 11; + if (simpleNumber < 1000000000000) return 12; + if (simpleNumber < 10000000000000) return 13; + if (simpleNumber < 100000000000000) return 14; + if (simpleNumber < 1000000000000000) return 15; + if (simpleNumber < 10000000000000000) return 16; + if (simpleNumber < 100000000000000000) return 17; + if (simpleNumber < 1000000000000000000) return 18; + return 19; + } + + /// Compute the raw integer digits which will then be printed with + /// grouping and translated to localized digits. + String _integerDigits(dynamic integerPart, dynamic extraIntegerDigits) { + // If the integer part is larger than the maximum integer size + // (2^52 on Javascript, 2^63 on the VM) it will lose precision, + // so pad out the rest of it with zeros. + var paddingDigits = ''; + if (integerPart is num && integerPart > _maxInt) { + var howManyDigitsTooBig = + (log(integerPart) / log(10)).ceil() - _maxDigits; + num divisor = pow(10, howManyDigitsTooBig).round(); + // pow() produces 0 if the result is too large for a 64-bit int. + // If that happens, use a floating point divisor instead. + if (divisor == 0) divisor = pow(10.0, howManyDigitsTooBig); + paddingDigits = '0' * howManyDigitsTooBig.toInt(); + integerPart = (integerPart / divisor).truncate(); + } + + var extra = extraIntegerDigits == 0 ? '' : extraIntegerDigits.toString(); + var intDigits = _mainIntegerDigits(integerPart); + var paddedExtra = intDigits.isEmpty ? extra : extra.padLeft(0, '0'); + return '$intDigits$paddedExtra$paddingDigits'; + } + + /// The digit string of the integer part. This is the empty string if the + /// integer part is zero and otherwise is the toString() of the integer + /// part, stripping off any minus sign. + String _mainIntegerDigits(dynamic integer) { + if (integer == 0) return ''; + var digits = integer.toString(); + // If we have a fixed-length int representation, it can have a negative + // number whose negation is also negative, e.g. 2^-63 in 64-bit. + // Remove the minus sign. + return digits.startsWith('-') ? digits.substring(1) : digits; + } + + /// Format the part after the decimal place in a fixed point number. + void _formatFractionPart(String fractionPart, int minDigits) { + var fractionLength = fractionPart.length; + while (fractionPart.codeUnitAt(fractionLength - 1) == '0'.codeUnitAt(0) && + fractionLength > minDigits + 1) { + fractionLength--; + } + for (var i = 1; i < fractionLength; i++) { + _addDigit(fractionPart.codeUnitAt(i)); + } + } + + /// Print the decimal separator if appropriate. + void _decimalSeparator(bool fractionPresent) { + if (fractionPresent) { + _add(_symbols.DECIMAL_SEP); + } + } + + /// Return true if we have a main integer part which is printable, either + /// because we have digits left of the decimal point (this may include digits + /// which have been moved left because of percent or permille formatting), + /// or because the minimum number of printable digits is greater than 1. + bool _hasIntegerDigits(String digits) => + digits.isNotEmpty || _minimumIntegerDigits > 0; + + /// A group of methods that provide support for writing digits and other + /// required characters into [_buffer] easily. + void _add(String x) { + _buffer.write(x); + } + + void _addZero() { + _buffer.write(_symbols.ZERO_DIGIT); + } + + void _addDigit(int x) { + _buffer.writeCharCode(x + _zeroOffset); + } +} + +// Suppress naming issues as changes would be breaking. +// ignore_for_file: non_constant_identifier_names +class _NumberSymbols { + final String DECIMAL_SEP, ZERO_DIGIT; + + const _NumberSymbols({required this.DECIMAL_SEP, required this.ZERO_DIGIT}); +} diff --git a/packages/dart/lib/src/utils/stacktrace_utils.dart b/packages/dart/lib/src/utils/stacktrace_utils.dart new file mode 100644 index 0000000000..99586ece73 --- /dev/null +++ b/packages/dart/lib/src/utils/stacktrace_utils.dart @@ -0,0 +1,18 @@ +import 'package:meta/meta.dart'; + +// A wrapper function around StackTrace.current so we can ignore it in the SDK +// crash detection. Otherwise, the SDK crash detection would have to ignore the +// method calling StackTrace.current, and it can't detect crashes in that +// method. +// You can read about the SDK crash detection here: +// https://github.com/getsentry/sentry/blob/master/src/sentry/utils/sdk_crashes/README.rst +@internal +StackTrace getCurrentStackTrace() => StackTrace.current; + +/// Regex that matches an “abs …” stack-frame line emitted by split-debug-info builds. +@internal +final absRegex = RegExp(r'^\s*#[0-9]+ +abs +([A-Fa-f0-9]+)'); + +/// Regex that matches the header of a stacktrace emitted by split-debug-info builds. +@internal +final buildIdRegex = RegExp(r"build_id[:=] *'([A-Fa-f0-9]+)'"); diff --git a/packages/dart/lib/src/utils/tracing_utils.dart b/packages/dart/lib/src/utils/tracing_utils.dart new file mode 100644 index 0000000000..bf283404ef --- /dev/null +++ b/packages/dart/lib/src/utils/tracing_utils.dart @@ -0,0 +1,128 @@ +import '../../sentry.dart'; + +SentryTraceHeader generateSentryTraceHeader( + {SentryId? traceId, SpanId? spanId, bool? sampled}) { + traceId ??= SentryId.newId(); + spanId ??= SpanId.newId(); + return SentryTraceHeader(traceId, spanId, sampled: sampled); +} + +void addTracingHeadersToHttpHeader(Map headers, Hub hub, + {ISentrySpan? span}) { + if (span != null) { + addSentryTraceHeaderFromSpan(span, headers); + addBaggageHeaderFromSpan( + span, + headers, + log: hub.options.log, + ); + } else { + addSentryTraceHeaderFromScope(hub.scope, headers); + addBaggageHeaderFromScope(hub.scope, headers, log: hub.options.log); + } +} + +void addSentryTraceHeaderFromScope(Scope scope, Map headers) { + final propagationContext = scope.propagationContext; + final traceHeader = propagationContext.toSentryTrace(); + headers[traceHeader.name] = traceHeader.value; +} + +void addSentryTraceHeaderFromSpan( + ISentrySpan span, Map headers) { + final traceHeader = span.toSentryTrace(); + headers[traceHeader.name] = traceHeader.value; +} + +void addSentryTraceHeader( + SentryTraceHeader traceHeader, Map headers) { + headers[traceHeader.name] = traceHeader.value; +} + +void addBaggageHeaderFromScope( + Scope scope, + Map headers, { + SdkLogCallback? log, +}) { + final baggageHeader = scope.propagationContext.toBaggageHeader(); + if (baggageHeader != null) { + addBaggageHeader(baggageHeader, headers, log: log); + } +} + +void addBaggageHeaderFromSpan( + ISentrySpan span, + Map headers, { + SdkLogCallback? log, +}) { + final baggage = span.toBaggageHeader(); + if (baggage != null) { + addBaggageHeader(baggage, headers, log: log); + } +} + +void addBaggageHeader( + SentryBaggageHeader baggage, + Map headers, { + SdkLogCallback? log, +}) { + final currentValue = headers[baggage.name] as String? ?? ''; + + final currentBaggage = SentryBaggage.fromHeader( + currentValue, + log: log, + ); + final sentryBaggage = SentryBaggage.fromHeader( + baggage.value, + log: log, + ); + + // overwrite sentry's keys https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/#baggage + final filteredBaggageHeader = Map.from(currentBaggage.keyValues); + filteredBaggageHeader.removeWhere((key, value) => key.startsWith('sentry-')); + + final mergedBaggage = { + ...filteredBaggageHeader, + ...sentryBaggage.keyValues, + }; + + final newBaggage = SentryBaggage(mergedBaggage, log: log); + + headers[baggage.name] = newBaggage.toHeaderString(); +} + +bool containsTargetOrMatchesRegExp( + List tracePropagationTargets, String url) { + if (tracePropagationTargets.isEmpty) { + return false; + } + for (final target in tracePropagationTargets) { + if (url.contains(target)) { + return true; + } + try { + final regExp = RegExp(target, caseSensitive: false); + if (regExp.hasMatch(url)) { + return true; + } + } on FormatException { + // ignore invalid regex + continue; + } + } + return false; +} + +bool isValidSampleRate(double? sampleRate) { + if (sampleRate == null) { + return false; + } + return !sampleRate.isNaN && sampleRate >= 0.0 && sampleRate <= 1.0; +} + +bool isValidSampleRand(double? sampleRand) { + if (sampleRand == null) { + return false; + } + return !sampleRand.isNaN && sampleRand >= 0.0 && sampleRand < 1.0; +} diff --git a/packages/dart/lib/src/utils/transport_utils.dart b/packages/dart/lib/src/utils/transport_utils.dart new file mode 100644 index 0000000000..3dae120d58 --- /dev/null +++ b/packages/dart/lib/src/utils/transport_utils.dart @@ -0,0 +1,45 @@ +import 'package:http/http.dart'; + +import '../client_reports/discard_reason.dart'; +import '../protocol.dart'; +import '../sentry_envelope.dart'; +import '../sentry_options.dart'; +import '../transport/data_category.dart'; + +class TransportUtils { + static void logResponse( + SentryOptions options, SentryEnvelope envelope, Response response, + {required String target}) { + if (response.statusCode != 200) { + if (options.debug) { + options.log( + SentryLevel.error, + 'Error, statusCode = ${response.statusCode}, body = ${response.body}', + ); + } + + if (response.statusCode >= 400 && response.statusCode != 429) { + for (final item in envelope.items) { + options.recorder.recordLostEvent( + DiscardReason.networkError, + DataCategory.fromItemType(item.header.type), + ); + + final originalObject = item.originalObject; + if (originalObject is SentryTransaction) { + options.recorder.recordLostEvent( + DiscardReason.networkError, + DataCategory.span, + count: originalObject.spans.length + 1, + ); + } + } + } + } else { + options.log( + SentryLevel.debug, + 'Envelope ${envelope.header.eventId ?? "--"} was sent successfully to $target.', + ); + } + } +} diff --git a/packages/dart/lib/src/utils/url_details.dart b/packages/dart/lib/src/utils/url_details.dart new file mode 100644 index 0000000000..4cc47510a6 --- /dev/null +++ b/packages/dart/lib/src/utils/url_details.dart @@ -0,0 +1,32 @@ +import 'package:meta/meta.dart'; +import '../../sentry.dart'; + +/// Sanitized url data for sentry.io +@internal +class UrlDetails { + UrlDetails({this.url, this.query, this.fragment}); + + final String? url; + final String? query; + final String? fragment; + + static const _unknown = 'unknown'; + + late final urlOrFallback = + Uri.tryParse(url ?? _unknown)?.toString() ?? _unknown; + + void applyToSpan(ISentrySpan? span) { + if (span == null) { + return; + } + if (url != null) { + span.setData('url', url); + } + if (query != null) { + span.setData("http.query", query); + } + if (fragment != null) { + span.setData("http.fragment", fragment); + } + } +} diff --git a/packages/dart/lib/src/version.dart b/packages/dart/lib/src/version.dart new file mode 100644 index 0000000000..6196fc630c --- /dev/null +++ b/packages/dart/lib/src/version.dart @@ -0,0 +1,33 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Sentry.io has a concept of "SDK", which refers to the client library or +/// tool used to submit events to Sentry.io. +/// +/// This library contains Sentry.io SDK constants used by this package. +library; + +/// The SDK version reported to Sentry.io in the submitted events. +const String sdkVersion = '9.6.0-beta.1'; + +String sdkName(bool isWeb) => isWeb ? _browserSdkName : _ioSdkName; + +/// The default SDK name reported to Sentry.io in the submitted events. +const String _ioSdkName = 'sentry.dart'; + +/// The SDK name for web projects reported to Sentry.io in the submitted events. +const String _browserSdkName = '$_ioSdkName.browser'; + +/// The name of the SDK platform reported to Sentry.io in the submitted events. +/// +/// Used for IO version. +String sdkPlatform(bool isWeb) => isWeb ? _browserPlatform : _ioSdkPlatform; + +/// The name of the SDK platform reported to Sentry.io in the submitted events. +/// +/// Used for IO version. +const String _ioSdkPlatform = 'other'; + +/// Used to report browser Stacktrace to sentry. +const String _browserPlatform = 'javascript'; diff --git a/packages/dart/pubspec.yaml b/packages/dart/pubspec.yaml new file mode 100644 index 0000000000..142b86ae9c --- /dev/null +++ b/packages/dart/pubspec.yaml @@ -0,0 +1,38 @@ +name: sentry +version: 9.6.0-beta.1 +description: > + A crash reporting library for Dart that sends crash reports to Sentry.io. + This library supports Dart VM and Web. For Flutter consider sentry_flutter instead. +homepage: https://docs.sentry.io/platforms/dart/ +repository: https://github.com/getsentry/sentry-dart +issue_tracker: https://github.com/getsentry/sentry-dart/issues +documentation: https://docs.sentry.io/platforms/dart/ + +environment: + sdk: '>=3.5.0 <4.0.0' + +platforms: + android: + ios: + macos: + linux: + windows: + web: + +dependencies: + http: '>=0.13.0 <2.0.0' + meta: ^1.3.0 + stack_trace: ^1.10.0 + uuid: '>=3.0.0 <5.0.0' + collection: ^1.16.0 + web: ^1.1.0 + +dev_dependencies: + build_runner: ^2.3.0 + mockito: ^5.1.0 + lints: '>=2.0.0' + test: ^1.21.1 + yaml: ^3.1.0 # needed for version match (code and pubspec) + coverage: ^1.3.0 + intl: '>=0.17.0 <1.0.0' + version: ^3.0.2 diff --git a/packages/dart/test/client_reports/client_report_recorder_test.dart b/packages/dart/test/client_reports/client_report_recorder_test.dart new file mode 100644 index 0000000000..3466d89f4d --- /dev/null +++ b/packages/dart/test/client_reports/client_report_recorder_test.dart @@ -0,0 +1,94 @@ +import 'package:sentry/src/client_reports/discard_reason.dart'; +import 'package:sentry/src/transport/data_category.dart'; +import 'package:test/test.dart'; + +import 'package:sentry/src/client_reports/client_report_recorder.dart'; + +void main() { + group(ClientReportRecorder, () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('flush returns null when there was nothing recorded', () { + final sut = fixture.getSut(); + + final clientReport = sut.flush(); + + expect(clientReport, null); + }); + + test('flush returns client report with current date', () { + final sut = fixture.getSut(); + + sut.recordLostEvent(DiscardReason.rateLimitBackoff, DataCategory.error); + + final clientReport = sut.flush(); + + expect(clientReport?.timestamp, DateTime(0)); + }); + + test('record lost event', () { + final sut = fixture.getSut(); + + sut.recordLostEvent(DiscardReason.rateLimitBackoff, DataCategory.error); + sut.recordLostEvent(DiscardReason.rateLimitBackoff, DataCategory.error); + + final clientReport = sut.flush(); + + final event = clientReport?.discardedEvents + .firstWhere((element) => element.category == DataCategory.error); + + expect(event?.reason, DiscardReason.rateLimitBackoff); + expect(event?.category, DataCategory.error); + expect(event?.quantity, 2); + }); + + test('record outcomes with different categories recorded separately', () { + final sut = fixture.getSut(); + + sut.recordLostEvent(DiscardReason.rateLimitBackoff, DataCategory.error); + sut.recordLostEvent( + DiscardReason.rateLimitBackoff, DataCategory.transaction); + + final clientReport = sut.flush(); + + final first = clientReport?.discardedEvents + .firstWhere((event) => event.category == DataCategory.error); + + final second = clientReport?.discardedEvents + .firstWhere((event) => event.category == DataCategory.transaction); + + expect(first?.reason, DiscardReason.rateLimitBackoff); + expect(first?.category, DataCategory.error); + expect(first?.quantity, 1); + + expect(second?.reason, DiscardReason.rateLimitBackoff); + expect(second?.category, DataCategory.transaction); + expect(second?.quantity, 1); + }); + + test('calling flush multiple times returns null', () { + final sut = fixture.getSut(); + + sut.recordLostEvent(DiscardReason.rateLimitBackoff, DataCategory.error); + + sut.flush(); + final clientReport = sut.flush(); + + expect(clientReport, null); + }); + }); +} + +class Fixture { + final _dateTimeProvider = () { + return DateTime(0); + }; + + ClientReportRecorder getSut() { + return ClientReportRecorder(_dateTimeProvider); + } +} diff --git a/packages/dart/test/client_reports/client_report_test.dart b/packages/dart/test/client_reports/client_report_test.dart new file mode 100644 index 0000000000..00f5736cc2 --- /dev/null +++ b/packages/dart/test/client_reports/client_report_test.dart @@ -0,0 +1,54 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/src/client_reports/client_report.dart'; +import 'package:sentry/src/client_reports/discard_reason.dart'; +import 'package:sentry/src/client_reports/discarded_event.dart'; +import 'package:sentry/src/transport/data_category.dart'; +import 'package:test/test.dart'; +import 'package:sentry/src/utils.dart'; + +void main() { + group('json', () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + }); + + test('toJson', () { + final sut = fixture.getSut(); + final json = sut.toJson(); + + expect( + DeepCollectionEquality().equals(fixture.clientReportJson, json), + true, + ); + }); + }); +} + +class Fixture { + final timestamp = DateTime.fromMillisecondsSinceEpoch(0); + late Map clientReportJson; + + Fixture() { + clientReportJson = { + 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), + 'discarded_events': [ + { + 'reason': 'ratelimit_backoff', + 'category': 'error', + 'quantity': 2, + } + ], + }; + } + + ClientReport getSut() { + return ClientReport( + timestamp, + [ + DiscardedEvent(DiscardReason.rateLimitBackoff, DataCategory.error, 2), + ], + ); + } +} diff --git a/packages/dart/test/contexts_test.dart b/packages/dart/test/contexts_test.dart new file mode 100644 index 0000000000..060207adf5 --- /dev/null +++ b/packages/dart/test/contexts_test.dart @@ -0,0 +1,331 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:convert'; + +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + group(Contexts, () { + final testBootTime = DateTime.fromMicrosecondsSinceEpoch(0); + + final testDevice = SentryDevice( + name: 'testDevice', + family: 'testFamily', + model: 'testModel', + modelId: 'testModelId', + arch: 'testArch', + batteryLevel: 23, + orientation: SentryOrientation.landscape, + manufacturer: 'testOEM', + brand: 'testBrand', + screenDensity: 99.1, + screenDpi: 100, + online: false, + charging: true, + lowMemory: false, + simulator: true, + memorySize: 1234567, + freeMemory: 12345, + usableMemory: 9876, + storageSize: 1234567, + freeStorage: 1234567, + externalStorageSize: 98765, + externalFreeStorage: 98765, + bootTime: testBootTime, + ); + final testOS = SentryOperatingSystem(name: 'testOS'); + final testRuntimes = [ + SentryRuntime(name: 'testRT1', version: '1.0'), + SentryRuntime(name: 'testRT2', version: '2.3.1'), + ]; + final testApp = SentryApp(version: '1.2.3'); + final testBrowser = SentryBrowser(version: '12.3.4'); + + final gpu = SentryGpu(name: 'Radeon', version: '1'); + + final flags = SentryFeatureFlags( + values: [ + SentryFeatureFlag(flag: 'feature_flag_1', result: true), + SentryFeatureFlag(flag: 'feature_flag_2', result: false), + ], + ); + + final contexts = Contexts( + device: testDevice, + operatingSystem: testOS, + runtimes: testRuntimes, + app: testApp, + browser: testBrowser, + gpu: gpu, + flags: flags, + ) + ..['theme'] = {'value': 'material'} + ..['version'] = {'value': 9}; + + final contextsJson = { + 'device': { + 'name': 'testDevice', + 'family': 'testFamily', + 'model': 'testModel', + 'model_id': 'testModelId', + 'arch': 'testArch', + 'battery_level': 23.0, + 'orientation': 'landscape', + 'manufacturer': 'testOEM', + 'brand': 'testBrand', + 'screen_density': 99.1, + 'screen_dpi': 100, + 'online': false, + 'charging': true, + 'low_memory': false, + 'simulator': true, + 'memory_size': 1234567, + 'free_memory': 12345, + 'usable_memory': 9876, + 'storage_size': 1234567, + 'free_storage': 1234567, + 'external_storage_size': 98765, + 'external_free_storage': 98765, + 'boot_time': testBootTime.toIso8601String(), + }, + 'os': { + 'name': 'testOS', + }, + 'app': {'app_version': '1.2.3'}, + 'browser': {'version': '12.3.4'}, + 'gpu': {'name': 'Radeon', 'version': '1'}, + 'testrt1': {'name': 'testRT1', 'type': 'runtime', 'version': '1.0'}, + 'testrt2': {'name': 'testRT2', 'type': 'runtime', 'version': '2.3.1'}, + 'theme': {'value': 'material'}, + 'version': {'value': 9}, + 'flags': { + 'values': [ + {'flag': 'feature_flag_1', 'result': true}, + {'flag': 'feature_flag_2', 'result': false}, + ] + }, + }; + + test('serializes to JSON', () { + final event = SentryEvent(contexts: contexts); + + expect(event.toJson()['contexts'], contextsJson); + }); + + test('deserializes/serializes JSON', () { + final contexts = Contexts.fromJson(contextsJson); + final json = contexts.toJson(); + + expect( + DeepCollectionEquality().equals(contextsJson, json), + true, + ); + }); + + test('clone context', () { + // ignore: deprecated_member_use_from_same_package + final clone = contexts.clone(); + + expect(clone.app!.toJson(), contexts.app!.toJson()); + expect(clone.browser!.toJson(), contexts.browser!.toJson()); + expect(clone.device!.toJson(), contexts.device!.toJson()); + expect( + clone.operatingSystem!.toJson(), contexts.operatingSystem!.toJson()); + expect(clone.gpu!.toJson(), contexts.gpu!.toJson()); + expect(clone.flags!.toJson(), contexts.flags!.toJson()); + for (final element in contexts.runtimes) { + expect( + clone.runtimes.where( + (clone) => MapEquality().equals(element.toJson(), clone.toJson()), + ), + isNotEmpty, + ); + } + + expect(clone['theme'], {'value': 'material'}); + expect(clone['version'], {'value': 9}); + }); + + test('set runtimes', () { + final contexts = Contexts(); + contexts.runtimes = [ + SentryRuntime(name: 'testRT1', version: '1.0'), + SentryRuntime(name: 'testRT2', version: '2.0'), + ]; + expect(contexts.runtimes.length, 2); + expect(contexts.runtimes.first.name, 'testRT1'); + expect(contexts.runtimes.first.version, '1.0'); + expect(contexts.runtimes.last.name, 'testRT2'); + expect(contexts.runtimes.last.version, '2.0'); + }); + + test('copyWith with contexts does not throw', () { + final contexts = Contexts( + runtimes: [ + SentryRuntime(name: 'testRT1', version: '1.0'), + ], + ); + // ignore: deprecated_member_use_from_same_package + final copy = contexts.copyWith(); + copy.addRuntime(SentryRuntime(name: 'testRT2', version: '2.0')); + + expect(copy.runtimes.length, 2); + expect(copy.runtimes.last.name, 'testRT2'); + }); + + test('can add runtime if runtime setter unmodifiable', () { + final contexts = Contexts(); + contexts.runtimes = List.unmodifiable([ + SentryRuntime(name: 'testRT1', version: '1.0'), + ]); + contexts.addRuntime(SentryRuntime(name: 'testRT2', version: '2.0')); + + expect(contexts.runtimes.length, 2); + expect(contexts.runtimes.last.name, 'testRT2'); + }); + + test('can add runtime if runtime ctor unmodifiable', () { + final contexts = Contexts( + runtimes: List.unmodifiable([ + SentryRuntime(name: 'testRT1', version: '1.0'), + ]), + ); + contexts.addRuntime(SentryRuntime(name: 'testRT2', version: '2.0')); + + expect(contexts.runtimes.length, 2); + expect(contexts.runtimes.last.name, 'testRT2'); + }); + + test('set flags', () { + final contexts = Contexts(); + contexts.flags = SentryFeatureFlags( + values: [ + SentryFeatureFlag(flag: 'feature_flag_1', result: true), + SentryFeatureFlag(flag: 'feature_flag_2', result: false), + ], + ); + expect(contexts.flags!.toJson(), flags.toJson()); + }); + }); + + group('parse contexts', () { + test('should parse json context', () { + final contexts = Contexts.fromJson(jsonDecode(jsonContexts)); + expect( + MapEquality().equals( + contexts.operatingSystem!.toJson(), + { + 'build': '19H2', + 'rooted': false, + 'kernel_version': + 'Darwin Kernel Version 19.6.0: Mon Aug 31 22:12:52 PDT 2020; root:xnu-6153.141.2~1/RELEASE_X86_64', + 'name': 'iOS', + 'version': '14.2' + }, + ), + true, + ); + expect( + MapEquality().equals(contexts.device!.toJson(), { + 'simulator': true, + 'model_id': 'simulator', + 'arch': 'x86', + 'free_memory': 232132608, + 'family': 'iOS', + 'model': 'iPhone13,4', + 'memory_size': 17179869184, + 'storage_size': 1023683072000, + 'boot_time': '2020-11-18T13:28:11.000Z', + 'usable_memory': 17114120192 + }), + true, + ); + + expect( + MapEquality().equals( + contexts.app!.toJson(), + { + 'app_id': 'D533244D-985D-3996-9FC2-9FA353D28586', + 'app_name': 'sentry_flutter_example', + 'app_version': '0.1.2', + 'app_identifier': 'io.sentry.flutter.example', + 'app_start_time': '2020-11-18T13:56:58.000Z', + 'device_app_hash': '59ca66aa7ac0bdc3d82f77041643036f6323bd6d', + 'app_build': '3', + 'build_type': 'simulator', + }, + ), + true, + ); + expect( + MapEquality().equals(contexts.runtimes.first.toJson(), { + 'name': 'testRT1', + 'version': '1.0', + 'raw_description': 'runtime description RT1 1.0' + }), + true, + ); + expect( + MapEquality().equals(contexts.browser!.toJson(), {'version': '12.3.4'}), + true, + ); + expect( + MapEquality() + .equals(contexts.gpu!.toJson(), {'name': 'Radeon', 'version': '1'}), + true, + ); + }); + }); +} + +const jsonContexts = ''' +{ + "os": { + "build": "19H2", + "rooted": false, + "kernel_version": "Darwin Kernel Version 19.6.0: Mon Aug 31 22:12:52 PDT 2020; root:xnu-6153.141.2~1/RELEASE_X86_64", + "name": "iOS", + "version": "14.2" + }, + "device": { + "simulator": true, + "model_id": "simulator", + "arch": "x86", + "free_memory": 232132608, + "family": "iOS", + "model": "iPhone13,4", + "memory_size": 17179869184, + "storage_size": 1023683072000, + "boot_time": "2020-11-18T13:28:11Z", + "usable_memory": 17114120192 + }, + "app": { + "app_id": "D533244D-985D-3996-9FC2-9FA353D28586", + "app_version": "0.1.2", + "app_identifier": "io.sentry.flutter.example", + "app_start_time": "2020-11-18T13:56:58Z", + "device_app_hash": "59ca66aa7ac0bdc3d82f77041643036f6323bd6d", + "app_build": "3", + "build_type": "simulator", + "app_name": "sentry_flutter_example" + }, + "runtime": + { + "name":"testRT1", + "version":"1.0", + "raw_description":"runtime description RT1 1.0" + }, + "browser": {"version": "12.3.4"}, + "gpu": {"name": "Radeon", "version": "1"}, + "flags": { + "values": [ + {"flag": "feature_flag_1", "result": true}, + {"flag": "feature_flag_2", "result": false} + ] + } +} +'''; diff --git a/packages/dart/test/diagnostic_logger_test.dart b/packages/dart/test/diagnostic_logger_test.dart new file mode 100644 index 0000000000..3bd5c46fe1 --- /dev/null +++ b/packages/dart/test/diagnostic_logger_test.dart @@ -0,0 +1,68 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/diagnostic_log.dart'; +import 'package:test/test.dart'; + +import 'test_utils.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + group(DiagnosticLog, () { + test('does not log if debug is disabled', () { + fixture.options.debug = false; + + fixture.getSut().log(SentryLevel.error, 'foobar'); + + expect(fixture.loggedMessage, isNull); + }); + + test('logs if debug is enabled', () { + fixture.options.debug = true; + + fixture.getSut().log(SentryLevel.error, 'foobar'); + + expect(fixture.loggedMessage, 'foobar'); + }); + + test('does not log if level is too low', () { + fixture.options.debug = true; + fixture.options.diagnosticLevel = SentryLevel.error; + + fixture.getSut().log(SentryLevel.warning, 'foobar'); + + expect(fixture.loggedMessage, isNull); + }); + + test('always logs fatal', () { + fixture.options.debug = false; + + fixture.getSut().log(SentryLevel.fatal, 'foobar'); + + expect(fixture.loggedMessage, 'foobar'); + }); + }); +} + +class Fixture { + var options = defaultTestOptions(); + + Object? loggedMessage; + + DiagnosticLog getSut() { + return DiagnosticLog(mockLogger, options); + } + + void mockLogger( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + loggedMessage = message; + } +} diff --git a/packages/dart/test/environment_test.dart b/packages/dart/test/environment_test.dart new file mode 100644 index 0000000000..d955f931e2 --- /dev/null +++ b/packages/dart/test/environment_test.dart @@ -0,0 +1,59 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; +import 'mocks/mock_environment_variables.dart'; +import 'test_utils.dart'; + +void main() { + // See https://docs.sentry.io/platforms/dart/configuration/options/ + // and https://github.com/getsentry/sentry-dart/issues/306 + group('Environment Variables', () { + tearDown(() async { + await Sentry.close(); + }); + + test('SentryOptions are not overriden by environment', () async { + final options = defaultTestOptions(); + options.release = 'release-1.2.3'; + options.dist = 'foo'; + options.environment = 'prod'; + options.environmentVariables = MockEnvironmentVariables( + dsn: 'foo-bar', + environment: 'staging', + release: 'release-9.8.7', + dist: 'bar', + ); + + await Sentry.init( + (options) => options, + options: options, + ); + + expect(options.dsn, testDsn); + expect(options.environment, 'prod'); + expect(options.release, 'release-1.2.3'); + expect(options.dist, 'foo'); + }); + + test('SentryOptions are overriden by environment', () async { + final options = defaultTestOptions()..dsn = null; + options.environmentVariables = MockEnvironmentVariables( + dsn: fakeDsn, + environment: 'staging', + release: 'release-9.8.7', + dist: 'bar', + ); + + await Sentry.init( + (options) => options, + options: options, + ); + + expect(options.dsn, fakeDsn); + expect(options.environment, 'staging'); + expect(options.release, 'release-9.8.7'); + expect(options.dist, 'bar'); + }); + }); +} diff --git a/packages/dart/test/event_processor/deduplication_event_processor_test.dart b/packages/dart/test/event_processor/deduplication_event_processor_test.dart new file mode 100644 index 0000000000..de576bafd4 --- /dev/null +++ b/packages/dart/test/event_processor/deduplication_event_processor_test.dart @@ -0,0 +1,123 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/event_processor/deduplication_event_processor.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; +import '../mocks/mock_hub.dart'; +import '../mocks/mock_transport.dart'; +import '../test_utils.dart'; + +void main() { + group('$DeduplicationEventProcessor', () { + final fixture = Fixture(); + + test('deduplicates if enabled', () { + final sut = fixture.getSut(true); + var ogEvent = _createEvent('foo'); + + expect(sut.apply(ogEvent, Hint()), isNotNull); + expect(sut.apply(ogEvent, Hint()), isNull); + }); + + test('does not deduplicate if disabled', () { + final sut = fixture.getSut(false); + var ogEvent = _createEvent('foo'); + + expect(sut.apply(ogEvent, Hint()), isNotNull); + expect(sut.apply(ogEvent, Hint()), isNotNull); + }); + + test('does not deduplicate if different events', () { + final sut = fixture.getSut(true); + var fooEvent = _createEvent('foo'); + var barEvent = _createEvent('bar'); + + expect(sut.apply(fooEvent, Hint()), isNotNull); + expect(sut.apply(barEvent, Hint()), isNotNull); + }); + + test('does not deduplicate transaction', () { + final sut = fixture.getSut(true); + final transaction = _createTransaction(fixture.hub); + + expect(sut.apply(transaction, Hint()), isNotNull); + expect(sut.apply(transaction, Hint()), isNotNull); + }); + + test('exceptions to keep for deduplication', () { + final sut = fixture.getSut(true, 2); + + var fooEvent = _createEvent('foo'); + var barEvent = _createEvent('bar'); + var fooBarEvent = _createEvent('foo bar'); + + expect(sut.apply(fooEvent, Hint()), isNotNull); + expect(sut.apply(barEvent, Hint()), isNotNull); + expect(sut.apply(fooBarEvent, Hint()), isNotNull); + expect(sut.apply(fooEvent, Hint()), isNotNull); + }); + + test('integration test', () async { + Future innerThrowingMethod() async { + try { + throw Exception('foo bar'); + } catch (e, stackTrace) { + await Sentry.captureException(e, stackTrace: stackTrace); + rethrow; + } + } + + Future outerThrowingMethod() async { + try { + await innerThrowingMethod(); + } catch (e, stackTrace) { + await Sentry.captureException(e, stackTrace: stackTrace); + } + } + + final transport = MockTransport(); + + await Sentry.init( + (options) { + options.dsn = fakeDsn; + options.transport = transport; + options.enableDeduplication = true; + }, + options: defaultTestOptions(), + ); + + // The test doesn't work if `outerTestMethod` is passed as + // `appRunner` callback + await outerThrowingMethod(); + + expect(transport.envelopes.length, 1); + + await Sentry.close(); + }); + }); +} + +SentryEvent _createEvent(String message) { + return SentryEvent(throwable: Exception(message)); +} + +SentryTransaction _createTransaction(Hub hub) { + final context = SentryTransactionContext('name', 'op'); + + final tracer = SentryTracer(context, hub); + return SentryTransaction(tracer); +} + +class Fixture { + final hub = MockHub(); + + DeduplicationEventProcessor getSut(bool enabled, + [int? maxDeduplicationItems]) { + final options = defaultTestOptions() + ..enableDeduplication = enabled + ..maxDeduplicationItems = maxDeduplicationItems ?? 5; + + return DeduplicationEventProcessor(options); + } +} diff --git a/packages/dart/test/event_processor/enricher/io_enricher_test.dart b/packages/dart/test/event_processor/enricher/io_enricher_test.dart new file mode 100644 index 0000000000..42460a3d81 --- /dev/null +++ b/packages/dart/test/event_processor/enricher/io_enricher_test.dart @@ -0,0 +1,268 @@ +@TestOn('vm') +library; + +import 'dart:io'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/event_processor/enricher/io_enricher_event_processor.dart'; +import 'package:sentry/src/platform/mock_platform.dart'; +import 'package:test/test.dart'; + +import '../../mocks.dart'; +import '../../test_utils.dart'; + +import 'package:sentry/src/utils/_io_get_sentry_operating_system.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('adds dart runtime', () async { + final enricher = fixture.getSut(); + final event = await enricher.apply(SentryEvent(), Hint()); + + expect(event?.contexts.runtimes, isNotEmpty); + final dartRuntime = event?.contexts.runtimes + .firstWhere((element) => element.name == 'Dart'); + expect(dartRuntime?.name, 'Dart'); + expect(dartRuntime?.rawDescription, isNotNull); + expect(dartRuntime!.version.toString(), isNot(Platform.version)); + expect(Platform.version, contains(dartRuntime.version.toString())); + }); + + test('does add to existing runtimes', () async { + final runtime = SentryRuntime(name: 'foo', version: 'bar'); + var event = SentryEvent(contexts: Contexts(runtimes: [runtime])); + final enricher = fixture.getSut(); + + event = (await enricher.apply(event, Hint()))!; + + expect(event.contexts.runtimes.contains(runtime), true); + // second runtime is Dart runtime + expect(event.contexts.runtimes.length, 2); + }); + + group('adds device, os and culture', () { + for (final hasNativeIntegration in [true, false]) { + test('native=$hasNativeIntegration', () async { + final enricher = + fixture.getSut(hasNativeIntegration: hasNativeIntegration); + final event = await enricher.apply(SentryEvent(), Hint()); + + expect(event?.contexts.device, isNotNull); + expect(event?.contexts.operatingSystem, isNotNull); + expect(event?.contexts.culture, isNotNull); + }); + } + }); + + test('device has no name if sendDefaultPii = false', () async { + final enricher = fixture.getSut(); + final event = await enricher.apply(SentryEvent(), Hint()); + + expect(event?.contexts.device?.name, isNull); + }); + + test('device has name if sendDefaultPii = true', () async { + final enricher = fixture.getSut(includePii: true); + final event = await enricher.apply(SentryEvent(), Hint()); + + expect(event?.contexts.device?.name, isNotNull); + }); + + test('culture has locale and timezone', () async { + final enricher = fixture.getSut(); + final event = await enricher.apply(SentryEvent(), Hint()); + + expect(event?.contexts.culture?.locale, isNotNull); + expect(event?.contexts.culture?.timezone, isNotNull); + }); + + test('os has name and version', () async { + final enricher = fixture.getSut(); + final event = await enricher.apply(SentryEvent(), Hint()); + + expect(event?.contexts.operatingSystem?.name, isNotNull); + if (Platform.isLinux) { + expect(event?.contexts.operatingSystem?.kernelVersion, isNotNull); + } else { + expect(event?.contexts.operatingSystem?.version, isNotNull); + } + }); + + group('os info parsing', () { + // See docs from [Platform.operatingSystemVersion]: + /// A string representing the version of the operating system or platform. + /// + /// The format of this string will vary by operating system, platform and + /// version and is not suitable for parsing. For example: + /// "Linux 5.11.0-1018-gcp #20~20.04.2-Ubuntu SMP Fri Sep 3 01:01:37 UTC 2021" + /// "Version 14.5 (Build 18E182)" + /// '"Windows 10 Pro" 10.0 (Build 19043)' + + Map parse(String name, String rawDescription) => + getSentryOperatingSystem(name: name, rawDescription: rawDescription) + .toJson(); + + test('android', () { + expect(parse('android', 'LYA-L29 10.1.0.289(C432E7R1P5)'), { + 'raw_description': 'LYA-L29 10.1.0.289(C432E7R1P5)', + 'name': 'Android', + 'build': 'LYA-L29 10.1.0.289(C432E7R1P5)', + }); + expect(parse('android', 'TE1A.220922.010'), { + 'raw_description': 'TE1A.220922.010', + 'name': 'Android', + 'build': 'TE1A.220922.010', + }); + }); + + test('linux', () { + expect( + parse('linux', + 'Linux 5.11.0-1018-gcp #20~20.04.2-Ubuntu SMP Fri Sep 3 01:01:37 UTC 2021'), + { + 'raw_description': + 'Linux 5.11.0-1018-gcp #20~20.04.2-Ubuntu SMP Fri Sep 3 01:01:37 UTC 2021', + 'name': 'Linux', + 'kernel_version': '5.11.0-1018-gcp', + 'build': '#20~20.04.2-Ubuntu SMP Fri Sep 3 01:01:37 UTC 2021', + }); + }); + + test('ios', () { + expect(parse('ios', 'Version 14.5 (Build 18E182)'), { + 'raw_description': 'Version 14.5 (Build 18E182)', + 'name': 'iOS', + 'version': '14.5', + 'build': '18E182', + }); + }); + + test('macos', () { + expect(parse('macos', 'Version 14.5 (Build 18E182)'), { + 'raw_description': 'Version 14.5 (Build 18E182)', + 'name': 'macOS', + 'version': '14.5', + 'build': '18E182', + }); + }); + + test('windows', () { + expect(parse('windows', '"Windows 10 Pro" 10.0 (Build 19043)'), { + 'raw_description': '"Windows 10 Pro" 10.0 (Build 19043)', + 'name': 'Windows', + 'version': '10.0', + 'build': '19043', + }); + }); + }); + + test('adds Dart context with PII', () async { + final enricher = fixture.getSut(includePii: true); + final event = await enricher.apply(SentryEvent(), Hint()); + + final dartContext = event?.contexts['dart_context']; + expect(dartContext, isNotNull); + // Getting the executable sometimes throws + //expect(dartContext['executable'], isNotNull); + expect(dartContext['resolved_executable'], isNotNull); + expect(dartContext['script'], isNotNull); + // package_config and executable_arguments are optional + }); + + test('adds Dart context without PII', () async { + final enricher = fixture.getSut(includePii: false); + final event = await enricher.apply(SentryEvent(), Hint()); + + final dartContext = event?.contexts['dart_context']; + expect(dartContext, isNotNull); + expect(dartContext['compile_mode'], isNotNull); + expect(dartContext['executable'], isNull); + expect(dartContext['resolved_executable'], isNull); + expect(dartContext['script'], isNull); + // package_config and executable_arguments are optional + // and Platform is not mockable + }); + + test('does not override event', () async { + final fakeEvent = SentryEvent( + contexts: Contexts( + device: SentryDevice( + name: 'device_name', + ), + operatingSystem: SentryOperatingSystem( + name: 'sentry_os', + version: 'best version', + ), + culture: SentryCulture( + locale: 'de', + timezone: 'timezone', + ), + ), + ); + + final enricher = fixture.getSut( + includePii: true, + hasNativeIntegration: false, + ); + + final event = await enricher.apply(fakeEvent, Hint()); + + // contexts.device + expect( + event?.contexts.device?.name, + fakeEvent.contexts.device?.name, + ); + // contexts.culture + expect( + event?.contexts.culture?.locale, + fakeEvent.contexts.culture?.locale, + ); + expect( + event?.contexts.culture?.timezone, + fakeEvent.contexts.culture?.timezone, + ); + // contexts.operatingSystem + expect( + event?.contexts.operatingSystem?.name, + fakeEvent.contexts.operatingSystem?.name, + ); + expect( + event?.contexts.operatingSystem?.version, + fakeEvent.contexts.operatingSystem?.version, + ); + }); + + test('$IoEnricherEventProcessor gets added on init', () async { + final options = defaultTestOptions(); + await Sentry.init( + (options) { + options.dsn = fakeDsn; + }, + options: options, + ); + await Sentry.close(); + + final ioEnricherCount = + options.eventProcessors.whereType().length; + expect(ioEnricherCount, 1); + }); +} + +class Fixture { + IoEnricherEventProcessor getSut({ + bool hasNativeIntegration = false, + bool includePii = false, + }) { + final options = defaultTestOptions() + ..platform = + hasNativeIntegration ? MockPlatform.iOS() : MockPlatform.fuchsia() + ..sendDefaultPii = includePii; + + return IoEnricherEventProcessor(options); + } +} diff --git a/packages/dart/test/event_processor/enricher/io_platform_memory_test.dart b/packages/dart/test/event_processor/enricher/io_platform_memory_test.dart new file mode 100644 index 0000000000..38b30e871d --- /dev/null +++ b/packages/dart/test/event_processor/enricher/io_platform_memory_test.dart @@ -0,0 +1,43 @@ +@TestOn('vm') +library; + +import 'dart:io'; + +import 'package:sentry/src/event_processor/enricher/io_platform_memory.dart'; +import 'package:test/test.dart'; + +import '../../test_utils.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('total physical memory', () async { + final sut = fixture.getSut(); + final totalPhysicalMemory = await sut.getTotalPhysicalMemory(); + + switch (Platform.operatingSystem) { + case 'linux': + expect(totalPhysicalMemory, isNotNull); + expect(totalPhysicalMemory! > 0, true); + break; + case 'windows': + expect(totalPhysicalMemory, isNotNull); + expect(totalPhysicalMemory! > 0, true); + break; + default: + expect(totalPhysicalMemory, isNull); + } + }); +} + +class Fixture { + var options = defaultTestOptions(); + + PlatformMemory getSut() { + return PlatformMemory(options); + } +} diff --git a/packages/dart/test/event_processor/enricher/web_enricher_test.dart b/packages/dart/test/event_processor/enricher/web_enricher_test.dart new file mode 100644 index 0000000000..1c3639ec42 --- /dev/null +++ b/packages/dart/test/event_processor/enricher/web_enricher_test.dart @@ -0,0 +1,207 @@ +@TestOn('browser') +library; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/event_processor/enricher/web_enricher_event_processor.dart'; +import 'package:sentry/src/platform/mock_platform.dart'; +import 'package:test/test.dart'; + +import '../../mocks.dart'; +import '../../test_utils.dart'; + +// can be tested on command line with +// `dart test -p chrome --name web_enricher` +void main() { + group('web_enricher', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('add path as transaction if transaction is null', () { + var enricher = fixture.getSut(); + final event = enricher.apply(SentryEvent(), Hint()); + + expect(event?.transaction, isNotNull); + }); + + test("don't overwrite transaction", () { + var enricher = fixture.getSut(); + final event = enricher.apply(SentryEvent(transaction: 'foobar'), Hint()); + + expect(event?.transaction, 'foobar'); + }); + + test('add request with user-agent header', () { + var enricher = fixture.getSut(); + final event = enricher.apply(SentryEvent(), Hint()); + + expect(event?.request?.headers['User-Agent'], isNotNull); + expect(event?.request?.url, isNotNull); + }); + + test('adds header to request if request already exists', () { + var event = SentryEvent( + request: SentryRequest( + url: 'foo.bar', + headers: { + 'foo': 'bar', + }, + ), + ); + var enricher = fixture.getSut(); + event = enricher.apply(event, Hint())!; + + expect(event.request?.headers['User-Agent'], isNotNull); + expect(event.request?.headers['foo'], 'bar'); + expect(event.request?.url, 'foo.bar'); + }); + + test('does not add auth headers to request', () { + var event = SentryEvent( + request: SentryRequest( + url: 'foo.bar', + headers: { + 'Authorization': 'foo', + 'authorization': 'bar', + }, + ), + ); + var enricher = fixture.getSut(); + event = enricher.apply(event, Hint())!; + + expect(event.request?.headers['Authorization'], isNull); + expect(event.request?.headers['authorization'], isNull); + }); + + test('user-agent is not overridden if already present', () { + var event = SentryEvent( + request: SentryRequest( + url: 'foo.bar', + headers: { + 'User-Agent': 'best browser agent', + }, + ), + ); + var enricher = fixture.getSut(); + event = enricher.apply(event, Hint())!; + + expect(event.request?.headers['User-Agent'], 'best browser agent'); + expect(event.request?.url, 'foo.bar'); + }); + + test('adds device and os', () { + var enricher = fixture.getSut(); + final event = enricher.apply(SentryEvent(), Hint()); + + expect(event?.contexts.device, isNotNull); + }); + + test('adds Dart context', () { + final enricher = fixture.getSut(); + final event = enricher.apply(SentryEvent(), Hint()); + + final dartContext = event?.contexts['dart_context']; + expect(dartContext, isNotNull); + expect(dartContext['compile_mode'], isNotNull); + }); + + test('device has screendensity', () { + var enricher = fixture.getSut(); + final event = enricher.apply(SentryEvent(), Hint()); + + expect(event?.contexts.device?.screenDensity, isNotNull); + }); + + test('culture has timezone', () { + var enricher = fixture.getSut(); + final event = enricher.apply(SentryEvent(), Hint()); + + expect(event?.contexts.culture?.timezone, isNotNull); + }); + + test('does not override event', () { + final fakeEvent = SentryEvent( + contexts: Contexts( + device: SentryDevice( + online: false, + memorySize: 200, + orientation: SentryOrientation.landscape, + screenHeightPixels: 1080, + screenWidthPixels: 1920, + screenDensity: 2, + ), + operatingSystem: SentryOperatingSystem( + name: 'sentry_os', + ), + culture: SentryCulture( + timezone: 'foo_timezone', + ), + ), + ); + + final enricher = fixture.getSut(); + + final event = enricher.apply(fakeEvent, Hint()); + + // contexts.device + expect( + event?.contexts.device?.online, + fakeEvent.contexts.device?.online, + ); + expect( + event?.contexts.device?.memorySize, + fakeEvent.contexts.device?.memorySize, + ); + expect( + event?.contexts.device?.orientation, + fakeEvent.contexts.device?.orientation, + ); + expect( + event?.contexts.device?.screenHeightPixels, + fakeEvent.contexts.device?.screenHeightPixels, + ); + expect( + event?.contexts.device?.screenWidthPixels, + fakeEvent.contexts.device?.screenWidthPixels, + ); + expect( + event?.contexts.device?.screenDensity, + fakeEvent.contexts.device?.screenDensity, + ); + // contexts.culture + expect( + event?.contexts.culture?.timezone, + fakeEvent.contexts.culture?.timezone, + ); + // contexts.operatingSystem + expect( + event?.contexts.operatingSystem?.name, + fakeEvent.contexts.operatingSystem?.name, + ); + }); + + test('$WebEnricherEventProcessor gets added on init', () async { + late SentryOptions sentryOptions; + await Sentry.init( + (options) { + options.dsn = fakeDsn; + sentryOptions = options; + }, + ); + await Sentry.close(); + + expect(sentryOptions.eventProcessors.map((e) => e.runtimeType.toString()), + contains('$WebEnricherEventProcessor')); + }); + }); +} + +class Fixture { + WebEnricherEventProcessor getSut() { + final options = defaultTestOptions() + ..platform = MockPlatform.fuchsia(); // Does not have native integration + return enricherEventProcessor(options) as WebEnricherEventProcessor; + } +} diff --git a/packages/dart/test/event_processor/exception/exception_group_event_processor_test.dart b/packages/dart/test/event_processor/exception/exception_group_event_processor_test.dart new file mode 100644 index 0000000000..b42f600c38 --- /dev/null +++ b/packages/dart/test/event_processor/exception/exception_group_event_processor_test.dart @@ -0,0 +1,453 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import 'package:sentry/src/event_processor/exception/exception_group_event_processor.dart'; + +import '../../test_utils.dart'; + +void main() { + group(ExceptionGroupEventProcessor, () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('does not group exceptions if groupExceptions is false', () { + final throwableA = Exception('ExceptionA'); + final exceptionA = SentryException( + type: 'ExceptionA', + value: 'ExceptionA', + throwable: throwableA, + mechanism: Mechanism(type: 'foo'), + ); + final throwableB = Exception('ExceptionB'); + final exceptionB = SentryException( + type: 'ExceptionB', + value: 'ExceptionB', + throwable: throwableB, + ); + exceptionA.addException(exceptionB); + + var event = SentryEvent( + throwable: throwableA, + exceptions: [exceptionA], + ); + + final sut = fixture.getSut(groupExceptions: false); + event = (sut.apply(event, Hint()))!; + + final sentryExceptionA = event.exceptions![0]; + final sentryExceptionB = event.exceptions![1]; + + expect(sentryExceptionB.throwable, throwableB); + expect(sentryExceptionB.mechanism?.type, isNull); + expect(sentryExceptionB.mechanism?.isExceptionGroup, isNull); + expect(sentryExceptionB.mechanism?.exceptionId, isNull); + expect(sentryExceptionB.mechanism?.parentId, isNull); + + expect(sentryExceptionA.throwable, throwableA); + expect(sentryExceptionA.mechanism?.type, "foo"); + expect(sentryExceptionA.mechanism?.isExceptionGroup, isNull); + expect(sentryExceptionA.mechanism?.exceptionId, isNull); + expect(sentryExceptionA.mechanism?.parentId, isNull); + }); + + test('applies grouping to exception with children', () { + final throwableA = Exception('ExceptionA'); + final exceptionA = SentryException( + type: 'ExceptionA', + value: 'ExceptionA', + throwable: throwableA, + ); + final throwableB = Exception('ExceptionB'); + final exceptionB = SentryException( + type: 'ExceptionB', + value: 'ExceptionB', + throwable: throwableB, + ); + exceptionA.addException(exceptionB); + + var event = SentryEvent( + throwable: throwableA, + exceptions: [exceptionA], + ); + + final sut = fixture.getSut(groupExceptions: true); + event = (sut.apply(event, Hint()))!; + + final sentryExceptionB = event.exceptions![0]; + final sentryExceptionA = event.exceptions![1]; + + expect(sentryExceptionB.throwable, throwableB); + expect(sentryExceptionB.mechanism?.type, "chained"); + expect(sentryExceptionB.mechanism?.isExceptionGroup, isNull); + expect(sentryExceptionB.mechanism?.exceptionId, 1); + expect(sentryExceptionB.mechanism?.parentId, 0); + + expect(sentryExceptionA.throwable, throwableA); + expect(sentryExceptionA.mechanism?.type, "generic"); + expect(sentryExceptionA.mechanism?.isExceptionGroup, isTrue); + expect(sentryExceptionA.mechanism?.exceptionId, 0); + expect(sentryExceptionA.mechanism?.parentId, isNull); + }); + + test('applies no grouping if there is no exception', () { + final event = SentryEvent(); + final sut = fixture.getSut(groupExceptions: true); + + final result = sut.apply(event, Hint()); + + expect(result, event); + expect(event.throwable, isNull); + expect(event.exceptions, isNull); + }); + + test('applies no grouping if there is already a list of exceptions', () { + final event = SentryEvent( + exceptions: [ + SentryException( + type: 'ExceptionA', + value: 'ExceptionA', + throwable: Exception('ExceptionA')), + SentryException( + type: 'ExceptionB', + value: 'ExceptionB', + throwable: Exception('ExceptionB')), + ], + ); + final sut = fixture.getSut(groupExceptions: true); + + final result = sut.apply(event, Hint()); + + final sentryExceptionA = result?.exceptions![0]; + final sentryExceptionB = result?.exceptions![1]; + + expect(sentryExceptionA?.type, 'ExceptionA'); + expect(sentryExceptionB?.type, 'ExceptionB'); + }); + }); + + group('flatten', () { + late Fixture fixture; + + final sentryException = SentryException( + type: 'type', + value: 'value', + module: 'module', + stackTrace: SentryStackTrace(frames: [SentryStackFrame(absPath: 'abs')]), + mechanism: Mechanism(type: 'type'), + threadId: 1, + ); + + setUp(() { + fixture = Fixture(); + }); + + test('will flatten exception with nested chained exceptions', () { + // ignore: deprecated_member_use_from_same_package + final origin = sentryException.copyWith( + value: 'origin', + ); + // ignore: deprecated_member_use_from_same_package + final originChild = sentryException.copyWith( + value: 'originChild', + ); + origin.addException(originChild); + + // ignore: deprecated_member_use_from_same_package + final originChildChild = sentryException.copyWith( + value: 'originChildChild', + ); + originChild.addException(originChildChild); + + final sut = fixture.getSut(groupExceptions: true); + var event = SentryEvent(exceptions: [origin]); + event = sut.apply(event, Hint())!; + final flattened = event.exceptions ?? []; + + expect(flattened.length, 3); + + expect(flattened[2].value, 'origin'); + expect(flattened[2].mechanism?.isExceptionGroup, isTrue); + expect(flattened[2].mechanism?.source, isNull); + expect(flattened[2].mechanism?.exceptionId, 0); + expect(flattened[2].mechanism?.parentId, null); + + expect(flattened[1].value, 'originChild'); + expect(flattened[1].mechanism?.isExceptionGroup, isTrue); + expect(flattened[1].mechanism?.source, isNull); + expect(flattened[1].mechanism?.exceptionId, 1); + expect(flattened[1].mechanism?.parentId, 0); + + expect(flattened[0].value, 'originChildChild'); + expect(flattened[0].mechanism?.isExceptionGroup, isNull); + expect(flattened[0].mechanism?.source, isNull); + expect(flattened[0].mechanism?.exceptionId, 2); + expect(flattened[0].mechanism?.parentId, 1); + }); + + test('will flatten exception with nested parallel exceptions', () { + // ignore: deprecated_member_use_from_same_package + final origin = sentryException.copyWith( + value: 'origin', + ); + // ignore: deprecated_member_use_from_same_package + final originChild = sentryException.copyWith( + value: 'originChild', + ); + origin.addException(originChild); + // ignore: deprecated_member_use_from_same_package + final originChild2 = sentryException.copyWith( + value: 'originChild2', + ); + origin.addException(originChild2); + + final sut = fixture.getSut(groupExceptions: true); + var event = SentryEvent(exceptions: [origin]); + event = sut.apply(event, Hint())!; + final flattened = event.exceptions ?? []; + + expect(flattened.length, 3); + + expect(flattened[2].value, 'origin'); + expect(flattened[2].mechanism?.isExceptionGroup, true); + expect(flattened[2].mechanism?.source, isNull); + expect(flattened[2].mechanism?.exceptionId, 0); + expect(flattened[2].mechanism?.parentId, null); + + expect(flattened[1].value, 'originChild'); + expect(flattened[1].mechanism?.source, isNull); + expect(flattened[1].mechanism?.exceptionId, 1); + expect(flattened[1].mechanism?.parentId, 0); + + expect(flattened[0].value, 'originChild2'); + expect(flattened[0].mechanism?.source, isNull); + expect(flattened[0].mechanism?.exceptionId, 2); + expect(flattened[0].mechanism?.parentId, 0); + }); + + test('will flatten rfc example', () { + // try: + // raise RuntimeError("something") + // except: + // raise ExceptionGroup("nested", + // [ + // ValueError(654), + // ExceptionGroup("imports", + // [ + // ImportError("no_such_module"), + // ModuleNotFoundError("another_module"), + // ] + // ), + // TypeError("int"), + // ] + // ) + + // https://github.com/getsentry/rfcs/blob/main/text/0079-exception-groups.md#example-event + // In the example, the runtime error is inserted as the first exception in the outer exception group. + + // ignore: deprecated_member_use_from_same_package + final exceptionGroupNested = sentryException.copyWith( + value: 'ExceptionGroup', + ); + // ignore: deprecated_member_use_from_same_package + final runtimeError = sentryException.copyWith( + value: 'RuntimeError', + // ignore: deprecated_member_use_from_same_package + mechanism: sentryException.mechanism?.copyWith(source: '__source__'), + ); + exceptionGroupNested.addException(runtimeError); + // ignore: deprecated_member_use_from_same_package + final valueError = sentryException.copyWith( + value: 'ValueError', + // ignore: deprecated_member_use_from_same_package + mechanism: sentryException.mechanism?.copyWith(source: 'exceptions[0]'), + ); + exceptionGroupNested.addException(valueError); + + // ignore: deprecated_member_use_from_same_package + final exceptionGroupImports = sentryException.copyWith( + value: 'ExceptionGroup', + // ignore: deprecated_member_use_from_same_package + mechanism: sentryException.mechanism?.copyWith(source: 'exceptions[1]'), + ); + exceptionGroupNested.addException(exceptionGroupImports); + + // ignore: deprecated_member_use_from_same_package + final importError = sentryException.copyWith( + value: 'ImportError', + // ignore: deprecated_member_use_from_same_package + mechanism: sentryException.mechanism?.copyWith(source: 'exceptions[0]'), + ); + exceptionGroupImports.addException(importError); + + // ignore: deprecated_member_use_from_same_package + final moduleNotFoundError = sentryException.copyWith( + value: 'ModuleNotFoundError', + // ignore: deprecated_member_use_from_same_package + mechanism: sentryException.mechanism?.copyWith(source: 'exceptions[1]'), + ); + exceptionGroupImports.addException(moduleNotFoundError); + + // ignore: deprecated_member_use_from_same_package + final typeError = sentryException.copyWith( + value: 'TypeError', + // ignore: deprecated_member_use_from_same_package + mechanism: sentryException.mechanism?.copyWith(source: 'exceptions[2]'), + ); + exceptionGroupNested.addException(typeError); + + final sut = fixture.getSut(groupExceptions: true); + var event = SentryEvent(exceptions: [exceptionGroupNested]); + event = sut.apply(event, Hint())!; + final flattened = event.exceptions ?? []; + + expect(flattened.length, 7); + + // { + // "exception": { + // "values": [ + // { + // "type": "TypeError", + // "value": "int", + // "mechanism": { + // "type": "chained", + // "source": "exceptions[2]", + // "exception_id": 6, + // "parent_id": 0 + // } + // }, + // { + // "type": "ModuleNotFoundError", + // "value": "another_module", + // "mechanism": { + // "type": "chained", + // "source": "exceptions[1]", + // "exception_id": 5, + // "parent_id": 3 + // } + // }, + // { + // "type": "ImportError", + // "value": "no_such_module", + // "mechanism": { + // "type": "chained", + // "source": "exceptions[0]", + // "exception_id": 4, + // "parent_id": 3 + // } + // }, + // { + // "type": "ExceptionGroup", + // "value": "imports", + // "mechanism": { + // "type": "chained", + // "source": "exceptions[1]", + // "is_exception_group": true, + // "exception_id": 3, + // "parent_id": 0 + // } + // }, + // { + // "type": "ValueError", + // "value": "654", + // "mechanism": { + // "type": "chained", + // "source": "exceptions[0]", + // "exception_id": 2, + // "parent_id": 0 + // } + // }, + // { + // "type": "RuntimeError", + // "value": "something", + // "mechanism": { + // "type": "chained", + // "source": "__context__", + // "exception_id": 1, + // "parent_id": 0 + // } + // }, + // { + // "type": "ExceptionGroup", + // "value": "nested", + // "mechanism": { + // "type": "exceptionhook", + // "handled": false, + // "is_exception_group": true, + // "exception_id": 0 + // } + // }, + // ] + // } + // } + + expect(flattened[0].value, 'TypeError'); + expect(flattened[0].mechanism?.source, 'exceptions[2]'); + expect(flattened[0].mechanism?.exceptionId, 6); + expect(flattened[0].mechanism?.parentId, 0); + expect(flattened[0].mechanism?.type, 'chained'); + + expect(flattened[1].value, 'ModuleNotFoundError'); + expect(flattened[1].mechanism?.source, 'exceptions[1]'); + expect(flattened[1].mechanism?.exceptionId, 5); + expect(flattened[1].mechanism?.parentId, 3); + expect(flattened[1].mechanism?.type, 'chained'); + + expect(flattened[2].value, 'ImportError'); + expect(flattened[2].mechanism?.source, 'exceptions[0]'); + expect(flattened[2].mechanism?.exceptionId, 4); + expect(flattened[2].mechanism?.parentId, 3); + expect(flattened[2].mechanism?.type, 'chained'); + + expect(flattened[3].value, 'ExceptionGroup'); + expect(flattened[3].mechanism?.source, 'exceptions[1]'); + expect(flattened[3].mechanism?.isExceptionGroup, true); + expect(flattened[3].mechanism?.exceptionId, 3); + expect(flattened[3].mechanism?.parentId, 0); + expect(flattened[3].mechanism?.type, 'chained'); + + expect(flattened[4].value, 'ValueError'); + expect(flattened[4].mechanism?.source, 'exceptions[0]'); + expect(flattened[4].mechanism?.exceptionId, 2); + expect(flattened[4].mechanism?.parentId, 0); + expect(flattened[4].mechanism?.type, 'chained'); + + expect(flattened[5].value, 'RuntimeError'); + expect(flattened[5].mechanism?.exceptionId, 1); + expect(flattened[5].mechanism?.parentId, 0); + expect(flattened[5].mechanism?.type, 'chained'); + + expect(flattened[6].value, 'ExceptionGroup'); + expect(flattened[6].mechanism?.isExceptionGroup, true); + expect(flattened[6].mechanism?.exceptionId, 0); + expect(flattened[6].mechanism?.parentId, isNull); + expect( + flattened[6].mechanism?.type, exceptionGroupNested.mechanism?.type); + }); + }); +} + +class Fixture { + final options = defaultTestOptions(); + + ExceptionGroupEventProcessor getSut({required bool groupExceptions}) { + options.groupExceptions = groupExceptions; + return ExceptionGroupEventProcessor(options); + } +} + +class ExceptionA { + ExceptionA(this.other); + final ExceptionB? other; +} + +class ExceptionB { + ExceptionB(this.anotherOther); + final ExceptionC? anotherOther; +} + +class ExceptionC { + // I am empty inside +} diff --git a/packages/dart/test/event_processor/exception/io_exception_event_processor_test.dart b/packages/dart/test/event_processor/exception/io_exception_event_processor_test.dart new file mode 100644 index 0000000000..109b07b8eb --- /dev/null +++ b/packages/dart/test/event_processor/exception/io_exception_event_processor_test.dart @@ -0,0 +1,132 @@ +@TestOn('vm') +library; + +import 'dart:io'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/event_processor/exception/io_exception_event_processor.dart'; +import 'package:test/test.dart'; +import 'package:sentry/src/sentry_exception_factory.dart'; + +import '../../test_utils.dart'; + +void main() { + group(IoExceptionEventProcessor, () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('adds $SentryRequest for $HttpException with uris', () { + final enricher = fixture.getSut(); + final event = enricher.apply( + SentryEvent( + throwable: HttpException( + '', + uri: Uri.parse('https://example.org/foo/bar?foo=bar'), + ), + ), + Hint(), + ); + + expect(event?.request, isNotNull); + expect(event?.request?.url, 'https://example.org/foo/bar'); + expect(event?.request?.queryString, 'foo=bar'); + }); + + test('no $SentryRequest for $HttpException without uris', () { + final enricher = fixture.getSut(); + final event = enricher.apply( + SentryEvent( + throwable: HttpException(''), + ), + Hint(), + ); + + expect(event?.request, isNull); + }); + + test('adds $SentryRequest for $SocketException with addresses', () { + final enricher = fixture.getSut(); + final throwable = SocketException( + 'Exception while connecting', + osError: OSError('Connection reset by peer', 54), + port: 12345, + address: InternetAddress( + '127.0.0.1', + type: InternetAddressType.IPv4, + ), + ); + final sentryException = + fixture.exceptionFactory.getSentryException(throwable); + + final event = enricher.apply( + SentryEvent( + throwable: throwable, + exceptions: [sentryException], + ), + Hint(), + ); + + expect(event?.request, isNotNull); + expect(event?.request?.url, '127.0.0.1'); + + final rootException = event?.exceptions?.first; + expect(rootException, sentryException); + + final childException = rootException?.exceptions?.first; + expect(childException?.type, 'OSError'); + expect(childException?.value, + 'OS Error: Connection reset by peer, errno = 54'); + expect(childException?.mechanism?.type, 'OSError'); + expect(childException?.mechanism?.meta['errno']['number'], 54); + expect(childException?.mechanism?.source, 'osError'); + }); + + test('adds OSError SentryException for $FileSystemException', () { + final enricher = fixture.getSut(); + final throwable = FileSystemException( + 'message', + 'path', + OSError('Oh no :(', 42), + ); + final sentryException = + fixture.exceptionFactory.getSentryException(throwable); + + final event = enricher.apply( + SentryEvent( + throwable: throwable, + exceptions: [sentryException], + ), + Hint(), + ); + + final rootException = event?.exceptions?.first; + expect(rootException, sentryException); + + final childException = rootException?.exceptions?.firstOrNull; + // Due to the test setup, there's no SentryException for the FileSystemException. + // And thus only one entry for the added OSError + expect(childException?.type, 'OSError'); + expect( + childException?.value, + 'OS Error: Oh no :(, errno = 42', + ); + expect(childException?.mechanism?.type, 'OSError'); + expect(childException?.mechanism?.meta['errno']['number'], 42); + expect(childException?.mechanism?.source, 'osError'); + }); + }); +} + +class Fixture { + final SentryOptions options = defaultTestOptions(); + + // ignore: invalid_use_of_internal_member + SentryExceptionFactory get exceptionFactory => options.exceptionFactory; + + IoExceptionEventProcessor getSut() { + return IoExceptionEventProcessor(SentryOptions.empty()); + } +} diff --git a/packages/dart/test/example_web_compile_test.dart b/packages/dart/test/example_web_compile_test.dart new file mode 100644 index 0000000000..5f3a422580 --- /dev/null +++ b/packages/dart/test/example_web_compile_test.dart @@ -0,0 +1,91 @@ +@TestOn('vm') +library; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:test/test.dart'; + +// Tests for the following issue +// https://github.com/getsentry/sentry-dart/issues/1893 +void main() { + final exampleAppDir = 'example_web'; + final exampleAppWorkingDir = + '${Directory.current.path}${Platform.pathSeparator}$exampleAppDir'; + group('Compile $exampleAppDir', () { + test( + 'dart pub get and compilation should run successfully', + () async { + final result = await _runProcess('dart pub get', + workingDirectory: exampleAppWorkingDir); + expect(result.exitCode, 0, + reason: 'Could run `dart pub get` for $exampleAppDir. ' + 'Likely caused by outdated dependencies'); + // running this test locally require clean working directory + final cleanResult = await _runProcess('dart run build_runner clean', + workingDirectory: exampleAppWorkingDir); + expect(cleanResult.exitCode, 0); + final compileResult = await _runProcess( + 'dart run build_runner build -r web -o build --delete-conflicting-outputs', + workingDirectory: exampleAppWorkingDir); + expect(compileResult.exitCode, 0, + reason: 'Could not compile $exampleAppDir project'); + expect( + compileResult.stdout, + isNot(contains( + 'Skipping compiling sentry_dart_web_example|web/main.dart')), + reason: + 'Could not compile main.dart, likely because of dart:io import.'); + expect( + compileResult.stdout, + anyOf( + contains('Built with build_runner'), + contains('build_web_compilers:entrypoint'), + ), + ); + }, + timeout: Timeout(const Duration(minutes: 1)), // double of detault timeout + ); + }); +} + +/// Runs [command] with command's stdout and stderr being forwrarded to +/// +/// test runner's respective streams. It buffers stdout and returns it. +/// +/// Returns [_CommandResult] with exitCode and stdout as a single sting +Future<_CommandResult> _runProcess(String command, + {String workingDirectory = '.'}) async { + final parts = command.split(' '); + assert(parts.isNotEmpty); + final cmd = parts[0]; + final args = parts.skip(1).toList(); + final process = + await Process.start(cmd, args, workingDirectory: workingDirectory); + // forward standard streams + unawaited(stderr.addStream(process.stderr)); + final buffer = []; + final stdoutCompleter = Completer.sync(); + process.stdout.listen( + (units) { + buffer.addAll(units); + stdout.add(units); + }, + cancelOnError: true, + onDone: () { + stdoutCompleter.complete(); + }, + ); + await stdoutCompleter.future; + final processOut = utf8.decode(buffer); + int exitCode = await process.exitCode; + return _CommandResult(exitCode: exitCode, stdout: processOut); +} + +class _CommandResult { + final int exitCode; + final String stdout; + + const _CommandResult({required this.exitCode, required this.stdout}); +} diff --git a/packages/dart/test/exception_identifier_test.dart b/packages/dart/test/exception_identifier_test.dart new file mode 100644 index 0000000000..382a90b5d8 --- /dev/null +++ b/packages/dart/test/exception_identifier_test.dart @@ -0,0 +1,186 @@ +import 'package:mockito/mockito.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/dart_exception_type_identifier.dart'; +import 'package:sentry/src/sentry_exception_factory.dart'; +import 'package:test/test.dart'; + +import 'mocks.mocks.dart'; +import 'mocks/mock_transport.dart'; +import 'sentry_client_test.dart'; +import 'test_utils.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + group('ExceptionTypeIdentifiers', () { + test('should be processed based on order in the list', () { + fixture.options + .prependExceptionTypeIdentifier(DartExceptionTypeIdentifier()); + fixture.options + .prependExceptionTypeIdentifier(ObfuscatedExceptionIdentifier()); + + final factory = SentryExceptionFactory(fixture.options); + final sentryException = factory.getSentryException(ObfuscatedException()); + + expect(sentryException.type, equals('ObfuscatedException')); + }); + + test('should return null if exception is not identified', () { + final identifier = DartExceptionTypeIdentifier(); + expect(identifier.identifyType(ObfuscatedException()), isNull); + }); + }); + + group('SentryExceptionFactory', () { + test('should process identifiers based on order in the list', () { + fixture.options + .prependExceptionTypeIdentifier(DartExceptionTypeIdentifier()); + fixture.options + .prependExceptionTypeIdentifier(ObfuscatedExceptionIdentifier()); + + final factory = SentryExceptionFactory(fixture.options); + final sentryException = factory.getSentryException(ObfuscatedException()); + + expect(sentryException.type, equals('ObfuscatedException')); + }); + + test('should use runtime type when identification is disabled', () { + fixture.options.enableExceptionTypeIdentification = false; + fixture.options + .prependExceptionTypeIdentifier(ObfuscatedExceptionIdentifier()); + + final factory = SentryExceptionFactory(fixture.options); + final sentryException = factory.getSentryException(ObfuscatedException()); + + expect(sentryException.type, equals('PlaceHolderException')); + }); + }); + + group('CachingExceptionTypeIdentifier', () { + late MockExceptionTypeIdentifier mockIdentifier; + late CachingExceptionTypeIdentifier cachingIdentifier; + + setUp(() { + mockIdentifier = MockExceptionTypeIdentifier(); + cachingIdentifier = CachingExceptionTypeIdentifier(mockIdentifier); + }); + + test('should return cached result for known types', () { + final exception = Exception('Test'); + when(mockIdentifier.identifyType(exception)).thenReturn('TestException'); + + expect( + cachingIdentifier.identifyType(exception), equals('TestException')); + expect( + cachingIdentifier.identifyType(exception), equals('TestException')); + expect( + cachingIdentifier.identifyType(exception), equals('TestException')); + + verify(mockIdentifier.identifyType(exception)).called(1); + }); + + test('should not cache unknown types', () { + final exception = ObfuscatedException(); + + when(mockIdentifier.identifyType(exception)).thenReturn(null); + + expect(cachingIdentifier.identifyType(exception), isNull); + expect(cachingIdentifier.identifyType(exception), isNull); + expect(cachingIdentifier.identifyType(exception), isNull); + + verify(mockIdentifier.identifyType(exception)).called(3); + }); + + test('should return null for unknown exception type', () { + final exception = Exception('Unknown'); + when(mockIdentifier.identifyType(exception)).thenReturn(null); + + expect(cachingIdentifier.identifyType(exception), isNull); + }); + + test('should handle different exception types separately', () { + final exception1 = Exception('Test1'); + final exception2 = FormatException('Test2'); + + when(mockIdentifier.identifyType(exception1)).thenReturn('Exception'); + when(mockIdentifier.identifyType(exception2)) + .thenReturn('FormatException'); + + expect(cachingIdentifier.identifyType(exception1), equals('Exception')); + expect(cachingIdentifier.identifyType(exception2), + equals('FormatException')); + + // Call again to test caching + expect(cachingIdentifier.identifyType(exception1), equals('Exception')); + expect(cachingIdentifier.identifyType(exception2), + equals('FormatException')); + + verify(mockIdentifier.identifyType(exception1)).called(1); + verify(mockIdentifier.identifyType(exception2)).called(1); + }); + }); + + group('Integration test', () { + setUp(() { + fixture.options.transport = fixture.mockTransport; + }); + + test( + 'should capture CustomException as exception type with custom identifier', + () async { + fixture.options + .prependExceptionTypeIdentifier(ObfuscatedExceptionIdentifier()); + + final client = SentryClient(fixture.options); + + await client.captureException(ObfuscatedException()); + + final transport = fixture.mockTransport; + final capturedEnvelope = transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect( + capturedEvent.exceptions!.first.type, equals('ObfuscatedException')); + }); + + test( + 'should capture PlaceHolderException as exception type without custom identifier', + () async { + final client = SentryClient(fixture.options); + + await client.captureException(ObfuscatedException()); + + final transport = fixture.mockTransport; + final capturedEnvelope = transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect( + capturedEvent.exceptions!.first.type, equals('PlaceHolderException')); + }); + }); +} + +class Fixture { + final mockTransport = MockTransport(); + SentryOptions options = defaultTestOptions(); +} + +// We use this PlaceHolder exception to mimic an obfuscated runtimeType +class PlaceHolderException implements Exception {} + +class ObfuscatedException implements Exception { + @override + Type get runtimeType => PlaceHolderException; +} + +class ObfuscatedExceptionIdentifier implements ExceptionTypeIdentifier { + @override + String? identifyType(dynamic throwable) { + if (throwable is ObfuscatedException) return 'ObfuscatedException'; + return null; + } +} diff --git a/packages/dart/test/feature_flags_integration_test.dart b/packages/dart/test/feature_flags_integration_test.dart new file mode 100644 index 0000000000..94d7cfb430 --- /dev/null +++ b/packages/dart/test/feature_flags_integration_test.dart @@ -0,0 +1,117 @@ +library; + +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; +import 'package:sentry/src/feature_flags_integration.dart'; + +import 'test_utils.dart'; +import 'mocks/mock_hub.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('adds itself to sdk.integrations', () { + final sut = fixture.getSut(); + + sut.call(fixture.hub, fixture.options); + + expect(fixture.options.sdk.integrations.contains('FeatureFlagsIntegration'), + isTrue); + }); + + test('adds feature flag to scope', () async { + final sut = fixture.getSut(); + + sut.call(fixture.hub, fixture.options); + + await sut.addFeatureFlag('foo', true); + + expect(fixture.hub.scope.contexts[SentryFeatureFlags.type], isNotNull); + expect( + fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.first.flag, + equals('foo')); + expect( + fixture + .hub.scope.contexts[SentryFeatureFlags.type]?.values.first.result, + equals(true)); + }); + + test('replaces existing feature flag', () async { + final sut = fixture.getSut(); + + sut.call(fixture.hub, fixture.options); + + await sut.addFeatureFlag('foo', true); + await sut.addFeatureFlag('foo', false); + + expect(fixture.hub.scope.contexts[SentryFeatureFlags.type], isNotNull); + expect( + fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.first.flag, + equals('foo')); + expect( + fixture + .hub.scope.contexts[SentryFeatureFlags.type]?.values.first.result, + equals(false)); + }); + + test('removes oldest feature flag when there are more than 100', () async { + final sut = fixture.getSut(); + + sut.call(fixture.hub, fixture.options); + + for (var i = 0; i < 100; i++) { + await sut.addFeatureFlag('foo_$i', i % 2 == 0 ? true : false); + } + + expect(fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.length, + equals(100)); + + expect( + fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.first.flag, + equals('foo_0')); + expect( + fixture + .hub.scope.contexts[SentryFeatureFlags.type]?.values.first.result, + equals(true)); + + expect( + fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.last.flag, + equals('foo_99')); + expect( + fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.last.result, + equals(false)); + + await sut.addFeatureFlag('foo_100', true); + + expect(fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.length, + equals(100)); + + expect( + fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.first.flag, + equals('foo_1')); + expect( + fixture + .hub.scope.contexts[SentryFeatureFlags.type]?.values.first.result, + equals(false)); + + expect( + fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.last.flag, + equals('foo_100')); + expect( + fixture.hub.scope.contexts[SentryFeatureFlags.type]?.values.last.result, + equals(true)); + }); +} + +class Fixture { + final hub = MockHub(); + final options = defaultTestOptions(); + + FeatureFlagsIntegration getSut() { + return FeatureFlagsIntegration(); + } +} diff --git a/packages/dart/test/hint_test.dart b/packages/dart/test/hint_test.dart new file mode 100644 index 0000000000..04c09a28a0 --- /dev/null +++ b/packages/dart/test/hint_test.dart @@ -0,0 +1,108 @@ +import 'package:sentry/src/hint.dart'; +import 'package:sentry/src/sentry_attachment/sentry_attachment.dart'; +import 'package:test/test.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('Hint init with map', () { + final hint = Hint.withMap({'fixture-key': 'fixture-value'}); + expect("fixture-value", hint.get("fixture-key")); + }); + + test('Hint set value is returned with get', () { + final hint = Hint(); + hint.set("hint1", "some string"); + + expect("some string", hint.get("hint1")); + }); + + test('Hint get returns null if not contained', () { + final hint = Hint(); + expect(hint.get("hint-does-not-exist"), null); + }); + + test('Hint set multiple times only keeps latest value', () { + final hint = Hint(); + + hint.set("hint1", "some string"); + hint.set("hint1", "a different string"); + + expect("a different string", hint.get("hint1")); + }); + + test('Hint removed value is not returned by get', () { + final hint = Hint(); + + hint.set("hint1", "some string"); + expect("some string", hint.get("hint1")); + + hint.remove("hint1"); + expect(hint.get("hint1"), null); + }); + + test('Hint remove leaves other values', () { + final hint = Hint(); + + hint.set("hint1", "some string"); + expect("some string", hint.get("hint1")); + hint.set("hint2", "another string"); + + hint.remove("hint1"); + expect(hint.get("hint1"), null); + expect("another string", hint.get("hint2")); + }); + + test('Hint clear removes all values', () { + final hint = Hint(); + + hint.set("hint1", "some string"); + hint.set("hint2", "another string"); + + hint.clear(); + expect(hint.get("hint1"), null); + expect(hint.get("hint2"), null); + }); + + test('clear does not remove attachments, screenshot & viewHierarchy', () { + final attachment = SentryAttachment.fromIntList([], "fixture-fileName"); + + final sut = fixture.givenSut(); + sut.attachments.add(attachment); + sut.screenshot = attachment; + sut.viewHierarchy = attachment; + + sut.clear(); + + expect(sut.attachments.contains(attachment), true); + expect(sut.screenshot, attachment); + expect(sut.viewHierarchy, attachment); + }); + + test('Hint init with map null fallback', () { + final hint = Hint.withMap({'fixture-key': null}); + expect("null", hint.get("fixture-key")); + }); + + test('Hint addAll with map null fallback', () { + final hint = Hint(); + hint.addAll({'fixture-key': null}); + expect("null", hint.get("fixture-key")); + }); + + test('Hint set with null value fallback', () { + final hint = Hint(); + hint.set("fixture-key", null); + expect("null", hint.get("fixture-key")); + }); +} + +class Fixture { + Hint givenSut() { + return Hint(); + } +} diff --git a/packages/dart/test/http_client/breadcrumb_client_test.dart b/packages/dart/test/http_client/breadcrumb_client_test.dart new file mode 100644 index 0000000000..73d60ef035 --- /dev/null +++ b/packages/dart/test/http_client/breadcrumb_client_test.dart @@ -0,0 +1,301 @@ +import 'dart:io'; + +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:mockito/mockito.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/http_client/breadcrumb_client.dart'; +import 'package:test/test.dart'; + +import '../mocks/mock_hub.dart'; + +final requestUri = Uri.parse('https://example.com/path?foo=bar#baz'); + +void main() { + group(BreadcrumbClient, () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('GET: happy path', () async { + final sut = + fixture.getSut(fixture.getClient(statusCode: 200, reason: 'OK')); + + final response = await sut.get(requestUri); + expect(response.statusCode, 200); + + expect(fixture.hub.addBreadcrumbCalls.length, 1); + final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; + + expect(breadcrumb.type, 'http'); + expect(breadcrumb.data?['url'], 'https://example.com/path'); + expect(breadcrumb.data?['method'], 'GET'); + expect(breadcrumb.data?['http.query'], 'foo=bar'); + expect(breadcrumb.data?['http.fragment'], 'baz'); + expect(breadcrumb.data?['status_code'], 200); + expect(breadcrumb.data?['reason'], 'OK'); + expect(breadcrumb.data?['duration'], isNotNull); + expect(breadcrumb.data?['request_body_size'], isNotNull); + expect(breadcrumb.data?['response_body_size'], isNotNull); + expect(breadcrumb.level, SentryLevel.info); + }); + + test('GET: happy path for 404', () async { + final sut = fixture + .getSut(fixture.getClient(statusCode: 404, reason: 'NOT FOUND')); + + final response = await sut.get(requestUri); + + expect(response.statusCode, 404); + + expect(fixture.hub.addBreadcrumbCalls.length, 1); + final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; + + expect(breadcrumb.type, 'http'); + expect(breadcrumb.data?['url'], 'https://example.com/path'); + expect(breadcrumb.data?['method'], 'GET'); + expect(breadcrumb.data?['http.query'], 'foo=bar'); + expect(breadcrumb.data?['http.fragment'], 'baz'); + expect(breadcrumb.data?['status_code'], 404); + expect(breadcrumb.data?['reason'], 'NOT FOUND'); + expect(breadcrumb.data?['duration'], isNotNull); + expect(breadcrumb.level, SentryLevel.warning); + }); + + test('POST: happy path', () async { + final sut = fixture.getSut(fixture.getClient(statusCode: 200)); + + final response = await sut.post(requestUri); + expect(response.statusCode, 200); + + expect(fixture.hub.addBreadcrumbCalls.length, 1); + final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; + + expect(breadcrumb.type, 'http'); + expect(breadcrumb.data?['url'], 'https://example.com/path'); + expect(breadcrumb.data?['method'], 'POST'); + expect(breadcrumb.data?['http.query'], 'foo=bar'); + expect(breadcrumb.data?['http.fragment'], 'baz'); + expect(breadcrumb.data?['status_code'], 200); + expect(breadcrumb.data?['duration'], isNotNull); + expect(breadcrumb.level, SentryLevel.info); + }); + + test('PUT: happy path', () async { + final sut = fixture.getSut(fixture.getClient(statusCode: 200)); + + final response = await sut.put(requestUri); + expect(response.statusCode, 200); + + expect(fixture.hub.addBreadcrumbCalls.length, 1); + final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; + + expect(breadcrumb.type, 'http'); + expect(breadcrumb.data?['url'], 'https://example.com/path'); + expect(breadcrumb.data?['method'], 'PUT'); + expect(breadcrumb.data?['http.query'], 'foo=bar'); + expect(breadcrumb.data?['http.fragment'], 'baz'); + expect(breadcrumb.data?['status_code'], 200); + expect(breadcrumb.data?['duration'], isNotNull); + expect(breadcrumb.level, SentryLevel.info); + }); + + test('DELETE: happy path', () async { + final sut = fixture.getSut(fixture.getClient(statusCode: 200)); + + final response = await sut.delete(requestUri); + expect(response.statusCode, 200); + + expect(fixture.hub.addBreadcrumbCalls.length, 1); + final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; + + expect(breadcrumb.type, 'http'); + expect(breadcrumb.data?['url'], 'https://example.com/path'); + expect(breadcrumb.data?['method'], 'DELETE'); + expect(breadcrumb.data?['http.query'], 'foo=bar'); + expect(breadcrumb.data?['http.fragment'], 'baz'); + expect(breadcrumb.data?['status_code'], 200); + expect(breadcrumb.data?['duration'], isNotNull); + expect(breadcrumb.level, SentryLevel.info); + }); + + test('server error response (500)', () async { + final sut = fixture.getSut( + fixture.getClient(statusCode: 500, reason: 'INTERNAL SERVER ERROR')); + + final response = await sut.get(requestUri); + + expect(response.statusCode, 500); + + expect(fixture.hub.addBreadcrumbCalls.length, 1); + final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; + + expect(breadcrumb.type, 'http'); + expect(breadcrumb.data?['url'], 'https://example.com/path'); + expect(breadcrumb.data?['method'], 'GET'); + expect(breadcrumb.data?['http.query'], 'foo=bar'); + expect(breadcrumb.data?['http.fragment'], 'baz'); + expect(breadcrumb.data?['status_code'], 500); + expect(breadcrumb.data?['reason'], 'INTERNAL SERVER ERROR'); + expect(breadcrumb.data?['duration'], isNotNull); + expect(breadcrumb.level, SentryLevel.error); + }); + + test('server redirect (3xx)', () async { + final sut = fixture.getSut( + fixture.getClient(statusCode: 308, reason: 'PERMANENT REDIRECT')); + + final response = await sut.get(requestUri); + + expect(response.statusCode, 308); + + expect(fixture.hub.addBreadcrumbCalls.length, 1); + final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; + + expect(breadcrumb.type, 'http'); + expect(breadcrumb.data?['url'], 'https://example.com/path'); + expect(breadcrumb.data?['method'], 'GET'); + expect(breadcrumb.data?['http.query'], 'foo=bar'); + expect(breadcrumb.data?['http.fragment'], 'baz'); + expect(breadcrumb.data?['status_code'], 308); + expect(breadcrumb.data?['reason'], 'PERMANENT REDIRECT'); + expect(breadcrumb.data?['duration'], isNotNull); + expect(breadcrumb.level, SentryLevel.info); + }); + + test('invalid status (>= 6xx)', () async { + final sut = fixture.getSut( + fixture.getClient(statusCode: 600, reason: 'UNKNOWN STATUS CODE')); + + final response = await sut.get(requestUri); + + expect(response.statusCode, 600); + + expect(fixture.hub.addBreadcrumbCalls.length, 1); + final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; + + expect(breadcrumb.type, 'http'); + expect(breadcrumb.data?['url'], 'https://example.com/path'); + expect(breadcrumb.data?['method'], 'GET'); + expect(breadcrumb.data?['http.query'], 'foo=bar'); + expect(breadcrumb.data?['http.fragment'], 'baz'); + expect(breadcrumb.data?['status_code'], 600); + expect(breadcrumb.data?['reason'], 'UNKNOWN STATUS CODE'); + expect(breadcrumb.data?['duration'], isNotNull); + expect(breadcrumb.level, SentryLevel.info); + }); + + /// Tests, that in case an exception gets thrown, that + /// no exception gets reported by Sentry, in case the user wants to + /// handle the exception + test('no captureException for ClientException', () async { + final sut = fixture.getSut(MockClient((request) async { + expect(request.url, requestUri); + throw ClientException('test', requestUri); + })); + + try { + await sut.get(requestUri); + fail('Method did not throw'); + } on ClientException catch (e) { + expect(e.message, 'test'); + expect(e.uri, requestUri); + } + + expect(fixture.hub.captureExceptionCalls.length, 0); + }); + + /// SocketException are only a thing on dart:io platforms. + /// otherwise this is equal to the test above + test('no captureException for SocketException', () async { + final sut = fixture.getSut(MockClient((request) async { + expect(request.url, requestUri); + throw SocketException('test'); + })); + + try { + await sut.get(requestUri); + fail('Method did not throw'); + } on SocketException catch (e) { + expect(e.message, 'test'); + } + + expect(fixture.hub.captureExceptionCalls.length, 0); + }); + + test('breadcrumb gets added when an exception gets thrown', () async { + final sut = fixture.getSut(MockClient((request) async { + expect(request.url, requestUri); + throw Exception('foo bar'); + })); + + try { + await sut.get(requestUri); + fail('Method did not throw'); + } on Exception catch (_) {} + + expect(fixture.hub.addBreadcrumbCalls.length, 1); + + final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; + + expect(breadcrumb.type, 'http'); + expect(breadcrumb.data?['url'], 'https://example.com/path'); + expect(breadcrumb.data?['method'], 'GET'); + expect(breadcrumb.data?['http.query'], 'foo=bar'); + expect(breadcrumb.data?['http.fragment'], 'baz'); + expect(breadcrumb.level, SentryLevel.error); + expect(breadcrumb.data?['duration'], isNotNull); + }); + + test('close does get called for user defined client', () async { + final mockHub = MockHub(); + + final mockClient = CloseableMockClient(); + + final client = BreadcrumbClient(client: mockClient, hub: mockHub); + client.close(); + + expect(mockHub.addBreadcrumbCalls.length, 0); + expect(mockHub.captureExceptionCalls.length, 0); + verify(mockClient.close()); + }); + + test('Breadcrumb has correct duration', () async { + final sut = fixture.getSut(MockClient((request) async { + expect(request.url, requestUri); + await Future.delayed(Duration(seconds: 1)); + return Response('', 200, reasonPhrase: 'OK'); + })); + + final response = await sut.get(requestUri); + expect(response.statusCode, 200); + + expect(fixture.hub.addBreadcrumbCalls.length, 1); + final breadcrumb = fixture.hub.addBreadcrumbCalls.first.crumb; + + var durationString = breadcrumb.data!['duration']! as String; + // we don't check for anything below a second + expect(durationString.startsWith('0:00:01'), true); + }); + }); +} + +class CloseableMockClient extends Mock implements BaseClient {} + +class Fixture { + BreadcrumbClient getSut([MockClient? client]) { + final mc = client ?? getClient(); + return BreadcrumbClient(client: mc, hub: hub); + } + + late MockHub hub = MockHub(); + + MockClient getClient({int statusCode = 200, String? reason}) { + return MockClient((request) async { + expect(request.url, requestUri); + return Response('', statusCode, reasonPhrase: reason); + }); + } +} diff --git a/packages/dart/test/http_client/failed_request_client_test.dart b/packages/dart/test/http_client/failed_request_client_test.dart new file mode 100644 index 0000000000..b48d7e5d4a --- /dev/null +++ b/packages/dart/test/http_client/failed_request_client_test.dart @@ -0,0 +1,405 @@ +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:mockito/mockito.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/http_client/failed_request_client.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; +import '../mocks/mock_hub.dart'; +import '../mocks/mock_transport.dart'; +import '../test_utils.dart'; + +final requestUri = Uri.parse('https://example.com?foo=bar#myFragment'); + +void main() { + group(FailedRequestClient, () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('no captured events when everything goes well', () async { + final sut = fixture.getSut( + client: fixture.getClient(statusCode: 200), + ); + + final response = await sut.get(requestUri); + expect(response.statusCode, 200); + + expect(fixture.transport.calls, 0); + }); + + test('exception gets reported if client throws', () async { + fixture._hub.options.captureFailedRequests = true; + fixture._hub.options.sendDefaultPii = true; + + final sut = fixture.getSut( + client: createThrowingClient(), + ); + + await expectLater( + () async => await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}), + throwsException, + ); + + expect(fixture.transport.calls, 1); + + final eventCall = fixture.transport.events.first; + final exception = eventCall.exceptions?.first; + final mechanism = exception?.mechanism; + + expect(exception?.stackTrace, isNotNull); + expect(exception?.stackTrace!.snapshot, isNull); + expect(mechanism?.type, 'SentryHttpClient'); + + final request = eventCall.request; + expect(request, isNotNull); + expect(request?.method, 'GET'); + expect(request?.url, 'https://example.com'); + expect(request?.queryString, 'foo=bar'); + expect(request?.fragment, 'myFragment'); + expect(request?.cookies, isNull); + expect(request?.headers, {}); + + // Response is not captured in case of exception + expect(eventCall.contexts.response, isNull); + }); + + test( + 'exception does not gets reported if client throws but override disables capture', + () async { + fixture._hub.options.captureFailedRequests = true; + fixture._hub.options.sendDefaultPii = true; + + final sut = fixture.getSut( + client: createThrowingClient(), + captureFailedRequests: false, + ); + + await expectLater( + () async => await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}), + throwsException, + ); + + expect(fixture.transport.calls, 0); + }); + + test('event not reported if disabled', () async { + fixture._hub.options.captureFailedRequests = false; + + final sut = fixture.getSut( + client: createThrowingClient(), + ); + + await expectLater( + () async => await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}), + throwsException, + ); + + expect(fixture.transport.calls, 0); + }); + + test('event reported if disabled but overridden', () async { + fixture._hub.options.captureFailedRequests = false; + + final sut = fixture.getSut( + client: createThrowingClient(), + captureFailedRequests: true, + ); + + await expectLater( + () async => await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}), + throwsException, + ); + + expect(fixture.transport.calls, 1); + }); + + test('event not reported if not within the targets', () async { + fixture._hub.options.captureFailedRequests = true; + + final sut = fixture.getSut( + client: fixture.getClient(statusCode: 500), + failedRequestTargets: const ["myapi.com"]); + + final response = await sut.get(requestUri); + + expect(response.statusCode, 500); + expect(fixture.transport.calls, 0); + }); + + test('exception gets reported if bad status code occurs', () async { + fixture._hub.options.sendDefaultPii = true; + + final sut = fixture.getSut( + client: fixture.getClient( + statusCode: 404, + body: 'foo', + headers: {'lorem': 'ipsum', 'set-cookie': 'foo=bar'}), + failedRequestStatusCodes: [SentryStatusCode(404)], + ); + + await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}); + + expect(fixture.transport.calls, 1); + + final eventCall = fixture.transport.events.first; + final exception = eventCall.exceptions?.first; + final mechanism = exception?.mechanism; + + expect(mechanism?.type, 'SentryHttpClient'); + expect( + mechanism?.description, + 'HTTP Client Error with status code: 404', + ); + + expect(exception?.type, 'SentryHttpClientError'); + expect( + exception?.value, + 'Exception: HTTP Client Error with status code: 404', + ); + expect(exception?.stackTrace?.snapshot, true); + + final request = eventCall.request; + expect(request, isNotNull); + expect(request?.method, 'GET'); + expect(request?.url, 'https://example.com'); + expect(request?.queryString, 'foo=bar'); + expect(request?.fragment, 'myFragment'); + expect(request?.cookies, isNull); + expect(request?.headers, {}); + + final response = eventCall.contexts.response!; + expect(response.bodySize, 3); + expect(response.statusCode, 404); + expect(response.headers, + equals({'lorem': 'ipsum', 'set-cookie': 'foo=bar'})); + expect(response.cookies, equals('foo=bar')); + }); + + test( + 'just one report on status code reporting with failing requests enabled', + () async { + final sut = fixture.getSut( + client: fixture.getClient(statusCode: 404), + failedRequestStatusCodes: [SentryStatusCode(404)], + ); + + await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}); + + expect(fixture.transport.calls, 1); + }); + + test('close does get called for user defined client', () async { + final mockHub = MockHub(); + + final mockClient = CloseableMockClient(); + + final client = FailedRequestClient(client: mockClient, hub: mockHub); + client.close(); + + expect(mockHub.addBreadcrumbCalls.length, 0); + expect(mockHub.captureExceptionCalls.length, 0); + verify(mockClient.close()); + }); + + test('pii is not send on exception', () async { + fixture._hub.options.captureFailedRequests = true; + final sut = fixture.getSut( + client: createThrowingClient(), + ); + + await expectLater( + () async => await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}), + throwsException, + ); + + final event = fixture.transport.events.first; + expect(fixture.transport.calls, 1); + expect(event.request, isNotNull); + expect(event.request?.headers.isEmpty, true); + expect(event.request?.cookies, isNull); + expect(event.request?.data, isNull); + expect(event.contexts.response, isNull); + }); + + test('removes authorization headers', () async { + fixture._hub.options.captureFailedRequests = true; + final sut = fixture.getSut( + client: createThrowingClient(), + ); + + await expectLater( + () async => await sut.get(requestUri, + headers: {'authorization': 'foo', 'Authorization': 'foo'}), + throwsException, + ); + + final event = fixture.transport.events.first; + expect(fixture.transport.calls, 1); + expect(event.request, isNotNull); + expect(event.request?.headers.isEmpty, true); + }); + + test('pii is not send on invalid status code', () async { + final sut = fixture.getSut( + client: fixture.getClient(statusCode: 404), + failedRequestStatusCodes: [SentryStatusCode(404)], + ); + + await sut.get(requestUri, headers: {'Cookie': 'foo=bar'}); + + final event = fixture.transport.events.first; + expect(fixture.transport.calls, 1); + expect(event.request, isNotNull); + expect(event.request?.headers.isEmpty, true); + expect(event.request?.cookies, isNull); + expect(event.request?.data, isNull); + expect(event.contexts.response, isNotNull); + expect(event.contexts.response?.headers.isEmpty, true); + }); + + test('request body is included according to $MaxRequestBodySize', () async { + final scenarios = [ + // // never + MaxBodySizeTestConfig(MaxRequestBodySize.never, 0, false), + MaxBodySizeTestConfig(MaxRequestBodySize.never, 4001, false), + MaxBodySizeTestConfig(MaxRequestBodySize.never, 10001, false), + // // always + MaxBodySizeTestConfig(MaxRequestBodySize.always, 0, true), + MaxBodySizeTestConfig(MaxRequestBodySize.always, 4001, true), + MaxBodySizeTestConfig(MaxRequestBodySize.always, 10001, true), + // // small + MaxBodySizeTestConfig(MaxRequestBodySize.small, 0, true), + MaxBodySizeTestConfig(MaxRequestBodySize.small, 4000, true), + MaxBodySizeTestConfig(MaxRequestBodySize.small, 4001, false), + // // medium + MaxBodySizeTestConfig(MaxRequestBodySize.medium, 0, true), + MaxBodySizeTestConfig(MaxRequestBodySize.medium, 4001, true), + MaxBodySizeTestConfig(MaxRequestBodySize.medium, 10000, true), + MaxBodySizeTestConfig(MaxRequestBodySize.medium, 10001, false), + ]; + + fixture._hub.options.captureFailedRequests = true; + fixture._hub.options.sendDefaultPii = true; + for (final scenario in scenarios) { + fixture._hub.options.maxRequestBodySize = scenario.maxBodySize; + fixture.transport.reset(); + + final sut = fixture.getSut( + client: createThrowingClient(), + ); + + final request = Request('GET', requestUri) + // This creates a a request of the specified size + ..bodyBytes = List.generate(scenario.contentLength, (index) => 0); + + await expectLater( + () async => await sut.send(request), + throwsException, + ); + + expect(fixture.transport.calls, 1); + + final eventCall = fixture.transport.events.first; + final capturedRequest = eventCall.request; + expect(capturedRequest, isNotNull); + expect(capturedRequest?.data, + scenario.shouldBeIncluded ? isNotNull : isNull); + } + }); + + test('request passed to hint', () async { + fixture._hub.options.captureFailedRequests = true; + + Request? failedRequest; + final client = MockClient( + (request) async { + failedRequest = request; + throw TestException(); + }, + ); + + final sut = fixture.getSut(client: client); + + Hint? eventHint; + fixture.options.addEventProcessor(FunctionEventProcessor((event, hint) { + eventHint = hint; + return event; + })); + + await expectLater( + () async => await sut.get(requestUri), + throwsException, + ); + + expect((eventHint?.get('request') as Request?)?.url, failedRequest?.url); + }); + }); +} + +MockClient createThrowingClient() { + return MockClient( + (request) async { + expect(request.url, requestUri); + throw TestException(); + }, + ); +} + +class CloseableMockClient extends Mock implements BaseClient {} + +class Fixture { + final options = defaultTestOptions(); + late Hub _hub; + final transport = MockTransport(); + Fixture() { + options.transport = transport; + _hub = Hub(options); + } + + FailedRequestClient getSut({ + MockClient? client, + List failedRequestStatusCodes = const [ + SentryStatusCode.defaultRange() + ], + List failedRequestTargets = const [".*"], + bool? captureFailedRequests, + }) { + final mc = client ?? getClient(); + return FailedRequestClient( + client: mc, + hub: _hub, + failedRequestStatusCodes: failedRequestStatusCodes, + failedRequestTargets: failedRequestTargets, + captureFailedRequests: captureFailedRequests); + } + + MockClient getClient( + {int statusCode = 200, + String body = '', + Map headers = const {}}) { + return MockClient((request) async { + expect(request.url, requestUri); + return Response(body, statusCode, headers: headers); + }); + } +} + +class TestException implements Exception {} + +class MaxBodySizeTestConfig { + MaxBodySizeTestConfig( + this.maxBodySize, + this.contentLength, + this.shouldBeIncluded, + ); + + final T maxBodySize; + final int contentLength; + final bool shouldBeIncluded; + + Matcher get matcher => shouldBeIncluded ? isNotNull : isNull; +} diff --git a/packages/dart/test/http_client/io_client_provider_test.dart b/packages/dart/test/http_client/io_client_provider_test.dart new file mode 100644 index 0000000000..211c3e3900 --- /dev/null +++ b/packages/dart/test/http_client/io_client_provider_test.dart @@ -0,0 +1,267 @@ +@TestOn('vm') +library; + +import 'dart:io'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/http_client/io_client_provider.dart'; +import 'package:test/test.dart'; + +import '../test_utils.dart'; + +void main() { + group('getClient', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('http proxy should call findProxyResult', () async { + fixture.options.proxy = SentryProxy( + type: SentryProxyType.http, + host: 'localhost', + port: 8080, + ); + + final sut = fixture.getSut(); + sut.getClient(fixture.options); + + expect(fixture.mockHttpClient.findProxyResult, + equals(fixture.options.proxy?.toPacString())); + }); + + test('direct proxy should call findProxyResult', () async { + fixture.options.proxy = SentryProxy(type: SentryProxyType.direct); + + final sut = fixture.getSut(); + sut.getClient(fixture.options); + + expect(fixture.mockHttpClient.findProxyResult, + equals(fixture.options.proxy?.toPacString())); + }); + + test('socks proxy should not call findProxyResult', () async { + fixture.options.proxy = SentryProxy( + type: SentryProxyType.socks, host: 'localhost', port: 8080); + + final sut = fixture.getSut(); + sut.getClient(fixture.options); + + expect(fixture.mockHttpClient.findProxyResult, isNull); + }); + + test('authenticated proxy http should call addProxyCredentials', () async { + fixture.options.proxy = SentryProxy( + type: SentryProxyType.http, + host: 'localhost', + port: 8080, + user: 'admin', + pass: '0000', + ); + + final sut = fixture.getSut(); + + sut.getClient(fixture.options); + + expect(fixture.mockHttpClient.addProxyCredentialsHost, + fixture.options.proxy?.host); + expect(fixture.mockHttpClient.addProxyCredentialsPort, + fixture.options.proxy?.port); + expect(fixture.mockHttpClient.addProxyCredentialsRealm, ''); + expect(fixture.mockUser, fixture.options.proxy?.user); + expect(fixture.mockPass, fixture.options.proxy?.pass); + expect(fixture.mockHttpClient.addProxyCredentialsBasic, isNotNull); + }); + }); +} + +class Fixture { + final options = defaultTestOptions(); + final mockHttpClient = MockHttpClient(); + + String? mockUser; + String? mockPass; + + IoClientProvider getSut() { + return IoClientProvider( + () { + return mockHttpClient; + }, + (user, pass) { + mockUser = user; + mockPass = pass; + return HttpClientBasicCredentials(user, pass); + }, + ); + } +} + +class MockHttpClient implements HttpClient { + @override + bool autoUncompress = false; + + @override + Duration? connectionTimeout; + + @override + Duration idleTimeout = Duration(seconds: 1); + + @override + int? maxConnectionsPerHost; + + @override + String? userAgent; + + @override + void addCredentials( + Uri url, String realm, HttpClientCredentials credentials) { + // TODO: implement addCredentials + } + + String? addProxyCredentialsHost; + int? addProxyCredentialsPort; + String? addProxyCredentialsRealm; + HttpClientBasicCredentials? addProxyCredentialsBasic; + + @override + void addProxyCredentials( + String host, int port, String realm, HttpClientCredentials credentials) { + addProxyCredentialsHost = host; + addProxyCredentialsPort = port; + addProxyCredentialsRealm = realm; + if (credentials is HttpClientBasicCredentials) { + addProxyCredentialsBasic = credentials; + } + } + + @override + set authenticate( + Future Function(Uri url, String scheme, String? realm)? f) { + // TODO: implement authenticate + } + + @override + set authenticateProxy( + Future Function( + String host, int port, String scheme, String? realm)? + f) { + // TODO: implement authenticateProxy + } + + @override + set badCertificateCallback( + bool Function(X509Certificate cert, String host, int port)? callback) { + // TODO: implement badCertificateCallback + } + + @override + void close({bool force = false}) { + // TODO: implement close + } + + @override + set connectionFactory( + Future> Function( + Uri url, String? proxyHost, int? proxyPort)? + f) { + // TODO: implement connectionFactory + } + + @override + Future delete(String host, int port, String path) { + // TODO: implement delete + throw UnimplementedError(); + } + + @override + Future deleteUrl(Uri url) { + // TODO: implement deleteUrl + throw UnimplementedError(); + } + + String? findProxyResult; + + @override + set findProxy(String Function(Uri url)? f) { + findProxyResult = f!(Uri(scheme: "http", host: "localhost", port: 8080)); + } + + @override + Future get(String host, int port, String path) { + // TODO: implement get + throw UnimplementedError(); + } + + @override + Future getUrl(Uri url) { + // TODO: implement getUrl + throw UnimplementedError(); + } + + @override + Future head(String host, int port, String path) { + // TODO: implement head + throw UnimplementedError(); + } + + @override + Future headUrl(Uri url) { + // TODO: implement headUrl + throw UnimplementedError(); + } + + @override + set keyLog(Function(String line)? callback) { + // TODO: implement keyLog + } + + @override + Future open( + String method, String host, int port, String path) { + // TODO: implement open + throw UnimplementedError(); + } + + @override + Future openUrl(String method, Uri url) { + // TODO: implement openUrl + throw UnimplementedError(); + } + + @override + Future patch(String host, int port, String path) { + // TODO: implement patch + throw UnimplementedError(); + } + + @override + Future patchUrl(Uri url) { + // TODO: implement patchUrl + throw UnimplementedError(); + } + + @override + Future post(String host, int port, String path) { + // TODO: implement post + throw UnimplementedError(); + } + + @override + Future postUrl(Uri url) { + // TODO: implement postUrl + throw UnimplementedError(); + } + + @override + Future put(String host, int port, String path) { + // TODO: implement put + throw UnimplementedError(); + } + + @override + Future putUrl(Uri url) { + // TODO: implement putUrl + throw UnimplementedError(); + } +} diff --git a/packages/dart/test/http_client/sentry_http_client_test.dart b/packages/dart/test/http_client/sentry_http_client_test.dart new file mode 100644 index 0000000000..f70c9fbcea --- /dev/null +++ b/packages/dart/test/http_client/sentry_http_client_test.dart @@ -0,0 +1,201 @@ +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:mockito/mockito.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/http_client/failed_request_client.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:test/test.dart'; + +import '../mocks/mock_hub.dart'; +import '../mocks/mock_transport.dart'; +import '../test_utils.dart'; + +final requestUri = Uri.parse('https://example.com'); + +void main() { + group(SentryHttpClient, () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test( + 'no captured events & one captured breadcrumb when everything goes well', + () async { + final sut = fixture.getSut( + client: fixture.getClient(statusCode: 200, reason: 'OK'), + ); + + final response = await sut.get(requestUri); + expect(response.statusCode, 200); + + expect(fixture.mockHub.captureEventCalls.length, 0); + expect(fixture.mockHub.addBreadcrumbCalls.length, 1); + }); + + test('no captured event with default config', () async { + fixture.mockHub.options.captureFailedRequests = false; + + final sut = fixture.getSut( + client: createThrowingClient(), + ); + + await expectLater(() async => await sut.get(requestUri), throwsException); + + expect(fixture.mockHub.captureEventCalls.length, 0); + expect(fixture.mockHub.addBreadcrumbCalls.length, 1); + }); + + test('captured event with override', () async { + fixture.mockHub.options.captureFailedRequests = false; + + final sut = fixture.getSut( + client: createThrowingClient(), + captureFailedRequests: true, + ); + + await expectLater(() async => await sut.get(requestUri), throwsException); + + expect(fixture.mockHub.captureEventCalls.length, 1); + }); + + test('one captured event with when enabling $FailedRequestClient', + () async { + fixture.mockHub.options.captureFailedRequests = true; + fixture.mockHub.options.recordHttpBreadcrumbs = true; + final sut = fixture.getSut( + client: createThrowingClient(), + ); + + await expectLater(() async => await sut.get(requestUri), throwsException); + + expect(fixture.mockHub.captureEventCalls.length, 1); + // The event should not have breadcrumbs from the BreadcrumbClient + expect(fixture.mockHub.captureEventCalls.first.event.breadcrumbs, null); + // The breadcrumb for the request should still be added for every + // following event. + expect(fixture.mockHub.addBreadcrumbCalls.length, 1); + }); + + test( + 'no captured event with when enabling $FailedRequestClient with override', + () async { + fixture.mockHub.options.captureFailedRequests = true; + final sut = fixture.getSut( + client: createThrowingClient(), + captureFailedRequests: false, + ); + + await expectLater(() async => await sut.get(requestUri), throwsException); + + expect(fixture.mockHub.captureEventCalls.length, 0); + }); + + test('close does get called for user defined client', () async { + final mockHub = MockHub(); + + final mockClient = CloseableMockClient(); + + final client = SentryHttpClient(client: mockClient, hub: mockHub); + client.close(); + + expect(mockHub.addBreadcrumbCalls.length, 0); + expect(mockHub.captureExceptionCalls.length, 0); + verify(mockClient.close()); + }); + + test('no captured span if tracing disabled', () async { + fixture.realHub.options.recordHttpBreadcrumbs = false; + final tr = fixture.realHub.startTransaction( + 'name', + 'op', + bindToScope: true, + ); + + final sut = fixture.getSut( + hub: fixture.realHub, + client: fixture.getClient(statusCode: 200, reason: 'OK'), + ); + final response = await sut.get(requestUri); + + await tr.finish(); + + expect(response.statusCode, 200); + expect(tr, isA()); + }); + + test('captured span if tracing enabled', () async { + fixture.realHub.options.tracesSampleRate = 1.0; + fixture.realHub.options.recordHttpBreadcrumbs = false; + final tr = fixture.realHub.startTransaction( + 'name', + 'op', + bindToScope: true, + ) as SentryTracer; + + final sut = fixture.getSut( + client: fixture.getClient(statusCode: 200, reason: 'OK'), + hub: fixture.realHub, + ); + final response = await sut.get(requestUri); + + await tr.finish(); + + expect(response.statusCode, 200); + expect(tr.children.length, 1); + expect(tr.children.first.context.operation, 'http.client'); + }); + }); +} + +MockClient createThrowingClient() { + return MockClient( + (request) async { + expect(request.url, requestUri); + throw TestException(); + }, + ); +} + +class CloseableMockClient extends Mock implements BaseClient {} + +class Fixture { + late MockHub mockHub; + late Hub realHub; + late MockTransport transport; + final options = defaultTestOptions(); + + Fixture() { + // For some tests the real hub is needed, for other the mock is enough + transport = MockTransport(); + options.transport = transport; + realHub = Hub(options); + mockHub = MockHub(); + } + + SentryHttpClient getSut({ + MockClient? client, + List badStatusCodes = const [], + bool? captureFailedRequests, + Hub? hub, + }) { + final mc = client ?? getClient(); + hub ??= mockHub; + return SentryHttpClient( + client: mc, + hub: hub, + failedRequestStatusCodes: badStatusCodes, + captureFailedRequests: captureFailedRequests, + ); + } + + MockClient getClient({int statusCode = 200, String? reason}) { + return MockClient((request) async { + expect(request.url, requestUri); + return Response('', statusCode, reasonPhrase: reason); + }); + } +} + +class TestException implements Exception {} diff --git a/packages/dart/test/http_client/tracing_client_test.dart b/packages/dart/test/http_client/tracing_client_test.dart new file mode 100644 index 0000000000..2e31e076d7 --- /dev/null +++ b/packages/dart/test/http_client/tracing_client_test.dart @@ -0,0 +1,299 @@ +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/http_client/tracing_client.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:test/test.dart'; + +import '../mocks/mock_transport.dart'; +import '../test_utils.dart'; + +final requestUri = Uri.parse('https://example.com?foo=bar#baz'); + +void main() { + group(TracingClient, () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('should add sdk integration on init when tracing is enabled', + () async { + fixture.getSut( + client: fixture.getClient(statusCode: 200, reason: 'OK'), + ); + + expect(fixture._hub.options.isTracingEnabled(), isTrue); + expect(fixture._hub.options.sdk.integrations, + contains(TracingClient.integrationName)); + }); + + test('should not add sdk integration on init when tracing is disabled', + () async { + fixture._hub.options.tracesSampleRate = null; + fixture.getSut( + client: fixture.getClient(statusCode: 200, reason: 'OK'), + ); + + expect(fixture._hub.options.isTracingEnabled(), isFalse); + expect(fixture._hub.options.sdk.integrations, + isNot(contains(TracingClient.integrationName))); + }); + + test('captured span if successful request', () async { + final sut = fixture.getSut( + client: fixture.getClient(statusCode: 200, reason: 'OK'), + ); + final tr = fixture._hub.startTransaction( + 'name', + 'op', + bindToScope: true, + ); + + await sut.get(requestUri); + + await tr.finish(); + + final tracer = (tr as SentryTracer); + final span = tracer.children.first; + + expect(span.status, SpanStatus.ok()); + expect(span.context.operation, 'http.client'); + expect(span.context.description, 'GET https://example.com'); + expect(span.data['http.request.method'], 'GET'); + expect(span.data['url'], 'https://example.com'); + expect(span.data['http.query'], 'foo=bar'); + expect(span.data['http.fragment'], 'baz'); + expect(span.data['http.response.status_code'], 200); + expect(span.data['http.response_content_length'], 2); + expect(span.origin, SentryTraceOrigins.autoHttpHttp); + }); + + test('finish span if errored request', () async { + final sut = fixture.getSut( + client: createThrowingClient(), + ); + final tr = fixture._hub.startTransaction( + 'name', + 'op', + bindToScope: true, + ); + + try { + await sut.get(requestUri); + } catch (_) { + // ignore + } + + await tr.finish(); + + final tracer = (tr as SentryTracer); + final span = tracer.children.first; + + expect(span.finished, isTrue); + }); + + test('associate exception to span if errored request', () async { + final sut = fixture.getSut( + client: createThrowingClient(), + ); + final tr = fixture._hub.startTransaction( + 'name', + 'op', + bindToScope: true, + ); + + dynamic exception; + try { + await sut.get(requestUri); + } catch (error) { + exception = error; + } + + await tr.finish(); + + final tracer = (tr as SentryTracer); + final span = tracer.children.first; + + expect(span.status, SpanStatus.internalError()); + expect(span.throwable, exception); + }); + + test('should add tracing headers from span when tracing enabled', () async { + final sut = fixture.getSut( + client: fixture.getClient(statusCode: 200, reason: 'OK'), + ); + final tr = fixture._hub.startTransaction( + 'name', + 'op', + bindToScope: true, + ); + + final response = await sut.get(requestUri); + + await tr.finish(); + + final tracer = (tr as SentryTracer); + expect(tracer.children.length, 1); + final span = tracer.children.first; + final baggageHeader = span.toBaggageHeader(); + final sentryTraceHeader = span.toSentryTrace(); + + expect( + response.request!.headers[baggageHeader!.name], baggageHeader.value); + expect(response.request!.headers[sentryTraceHeader.name], + sentryTraceHeader.value); + }); + + test( + 'should add tracing headers from propagation context when tracing disabled', + () async { + fixture._hub.options.tracesSampleRate = null; + fixture._hub.options.tracesSampler = null; + final sut = fixture.getSut( + client: fixture.getClient(statusCode: 200, reason: 'OK'), + ); + final propagationContext = fixture._hub.scope.propagationContext; + propagationContext.baggage = SentryBaggage({'foo': 'bar'}); + + final response = await sut.get(requestUri); + + final baggageHeader = propagationContext.toBaggageHeader(); + + expect(propagationContext.toBaggageHeader(), isNotNull); + expect( + response.request!.headers[baggageHeader!.name], baggageHeader.value); + + final traceHeader = SentryTraceHeader.fromTraceHeader( + response.request!.headers['sentry-trace'] as String, + ); + expect(traceHeader.traceId, propagationContext.traceId); + // can't check span id as it is always generated new + }); + + test( + 'tracing header from propagation context should generate new span ids for new events', + () async { + fixture._hub.options.tracesSampleRate = null; + fixture._hub.options.tracesSampler = null; + final sut = fixture.getSut( + client: fixture.getClient(statusCode: 200, reason: 'OK'), + ); + final propagationContext = fixture._hub.scope.propagationContext; + propagationContext.baggage = SentryBaggage({'foo': 'bar'}); + + final response1 = await sut.get(requestUri); + final response2 = await sut.get(requestUri); + + final traceHeader1 = SentryTraceHeader.fromTraceHeader( + response1.request!.headers['sentry-trace']!); + final traceHeader2 = SentryTraceHeader.fromTraceHeader( + response2.request!.headers['sentry-trace']!); + + expect(traceHeader1.spanId, isNot(traceHeader2.spanId)); + }); + + test( + 'should not add tracing headers when URL does not match tracePropagationTargets with tracing enabled', + () async { + final sut = fixture.getSut( + client: fixture.getClient( + statusCode: 200, + reason: 'OK', + ), + tracePropagationTargets: ['nope'], + ); + final tr = fixture._hub.startTransaction( + 'name', + 'op', + bindToScope: true, + ); + + final response = await sut.get(requestUri); + + await tr.finish(); + + final tracer = (tr as SentryTracer); + final span = tracer.children.first; + final baggageHeader = span.toBaggageHeader(); + final sentryTraceHeader = span.toSentryTrace(); + + expect(response.request!.headers[baggageHeader!.name], isNull); + expect(response.request!.headers[sentryTraceHeader.name], isNull); + }); + + test( + 'should not add tracing headers when URL does not match tracePropagationTargets with tracing disabled', + () async { + fixture._hub.options.tracesSampleRate = null; + fixture._hub.options.tracesSampler = null; + final sut = fixture.getSut( + client: fixture.getClient(statusCode: 200, reason: 'OK'), + tracePropagationTargets: ['nope'], + ); + final propagationContext = fixture._hub.scope.propagationContext; + propagationContext.baggage = SentryBaggage({'foo': 'bar'}); + + final response = await sut.get(requestUri); + + final baggageHeader = propagationContext.toBaggageHeader(); + final sentryTraceHeader = propagationContext.toSentryTrace(); + + expect(response.request!.headers[baggageHeader!.name], isNull); + expect(response.request!.headers[sentryTraceHeader.name], isNull); + }); + + test('do not throw if no span bound to the scope', () async { + final sut = fixture.getSut( + client: fixture.getClient(statusCode: 200, reason: 'OK'), + ); + + await sut.get(requestUri); + }); + }); +} + +MockClient createThrowingClient() { + return MockClient( + (request) async { + expect(request.url, requestUri); + throw TestException(); + }, + ); +} + +class Fixture { + final _options = defaultTestOptions(); + late Hub _hub; + final transport = MockTransport(); + Fixture() { + _options.transport = transport; + _options.tracesSampleRate = 1.0; + _hub = Hub(_options); + } + + TracingClient getSut({ + MockClient? client, + List? tracePropagationTargets, + }) { + if (tracePropagationTargets != null) { + _hub.options.tracePropagationTargets.clear(); + _hub.options.tracePropagationTargets.addAll(tracePropagationTargets); + } + final mc = client ?? getClient(); + return TracingClient( + client: mc, + hub: _hub, + ); + } + + MockClient getClient({int statusCode = 200, String? reason}) { + return MockClient((request) async { + expect(request.url, requestUri); + return Response('{}', statusCode, reasonPhrase: reason, request: request); + }); + } +} + +class TestException implements Exception {} diff --git a/packages/dart/test/hub_test.dart b/packages/dart/test/hub_test.dart new file mode 100644 index 0000000000..eec9bded6d --- /dev/null +++ b/packages/dart/test/hub_test.dart @@ -0,0 +1,925 @@ +import 'package:collection/collection.dart'; +import 'package:mockito/mockito.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/client_reports/discard_reason.dart'; +import 'package:sentry/src/propagation_context.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:sentry/src/transport/data_category.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; +import 'mocks.mocks.dart'; +import 'mocks/mock_client_report_recorder.dart'; +import 'mocks/mock_sentry_client.dart'; +import 'test_utils.dart'; + +void main() { + bool scopeEquals(Scope? a, Scope b) { + return identical(a, b) || + a!.level == b.level && + a.transaction == b.transaction && + a.user == b.user && + IterableEquality().equals(a.fingerprint, b.fingerprint) && + IterableEquality().equals(a.breadcrumbs, b.breadcrumbs) && + MapEquality().equals(a.tags, b.tags) && + MapEquality().equals(a.extra, b.extra); + } + + group('Hub instantiation', () { + test('should instantiate with a dsn', () { + final hub = Hub(defaultTestOptions()); + expect(hub.isEnabled, true); + }); + }); + + group('Hub captures', () { + late Fixture fixture; + late SentryEvent fakeEvent; + + setUp(() { + fixture = Fixture(); + fakeEvent = getFakeEvent(); + }); + + test( + 'should capture event with the default scope', + () async { + final hub = fixture.getSut(); + await hub.captureEvent(fakeEvent); + + var scope = fixture.client.captureEventCalls.first.scope; + + expect( + fixture.client.captureEventCalls.first.event, + fakeEvent, + ); + + expect(scopeEquals(scope, Scope(fixture.options)), true); + }, + ); + + test( + 'should capture feedback with the default scope', + () async { + final hub = fixture.getSut(); + final feedback = SentryFeedback(message: 'message'); + await hub.captureFeedback(feedback); + + var scope = fixture.client.captureFeedbackCalls.first.scope; + + expect( + fixture.client.captureFeedbackCalls.first.feedback, + feedback, + ); + + expect(scopeEquals(scope, Scope(fixture.options)), true); + }, + ); + + test('should capture exception', () async { + final hub = fixture.getSut(); + await hub.captureException(fakeException); + + expect(fixture.client.captureEventCalls.length, 1); + expect( + fixture.client.captureEventCalls.first.event.throwable, + fakeException, + ); + expect(fixture.client.captureEventCalls.first.scope, isNotNull); + }); + + test('should capture exception with message', () async { + final hub = fixture.getSut(); + await hub.captureException(fakeException, + message: SentryMessage('Sentry rocks')); + + expect(fixture.client.captureEventCalls.first.event.message?.formatted, + 'Sentry rocks'); + }); + + test('should capture message', () async { + final hub = fixture.getSut(); + final fakeMessage = getFakeMessage(); + + await hub.captureMessage( + fakeMessage.formatted, + level: SentryLevel.warning, + ); + + expect(fixture.client.captureMessageCalls.length, 1); + expect(fixture.client.captureMessageCalls.first.formatted, + fakeMessage.formatted); + expect( + fixture.client.captureMessageCalls.first.level, SentryLevel.warning); + expect(fixture.client.captureMessageCalls.first.scope, isNotNull); + }); + + test('should save the lastEventId', () async { + final hub = fixture.getSut(); + final event = SentryEvent(); + final eventId = event.eventId; + final returnedId = await hub.captureEvent(event); + expect(eventId.toString(), returnedId.toString()); + }); + + test('capture event should assign trace context', () async { + final hub = fixture.getSut(); + + final event = SentryEvent(throwable: fakeException); + final span = NoOpSentrySpan(); + hub.setSpanContext(fakeException, span, 'test'); + + await hub.captureEvent(event); + final capturedEvent = fixture.client.captureEventCalls.first; + + expect(capturedEvent.event.transaction, 'test'); + expect(capturedEvent.event.contexts.trace, isNotNull); + }); + + test('capture exception should assign trace context', () async { + final hub = fixture.getSut(); + + final span = NoOpSentrySpan(); + hub.setSpanContext(fakeException, span, 'test'); + + await hub.captureException(fakeException); + final capturedEvent = fixture.client.captureEventCalls.first; + + expect(capturedEvent.event.transaction, 'test'); + expect(capturedEvent.event.contexts.trace, isNotNull); + }); + + test('capture exception should assign sampled trace context', () async { + final hub = fixture.getSut(); + + final span = SentrySpan( + fixture.tracer, + fixture._context, + hub, + samplingDecision: fixture._context.samplingDecision, + ); + hub.setSpanContext(fakeException, span, 'test'); + + await hub.captureException(fakeException); + final capturedEvent = fixture.client.captureEventCalls.first; + + expect(capturedEvent.event.contexts.trace, isNotNull); + expect(capturedEvent.event.contexts.trace!.sampled, isTrue); + }); + + test('Expando does not throw when exception type is not supported', + () async { + final hub = fixture.getSut(); + + try { + throw 'string error'; + } catch (exception, _) { + final event = SentryEvent(throwable: exception); + final span = NoOpSentrySpan(); + hub.setSpanContext(exception, span, 'test'); + + await hub.captureEvent(event); + } + + final capturedEvent = fixture.client.captureEventCalls.first; + + expect(capturedEvent.event.transaction, 'test'); + expect(capturedEvent.event.contexts.trace, isNotNull); + }); + }); + + group('Hub transactions', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('start transaction with given name, op, desc and start time', + () async { + final hub = fixture.getSut(); + final startTime = DateTime.now(); + + final tr = hub.startTransaction( + 'name', + 'op', + startTimestamp: startTime, + description: 'desc', + ); + + expect(tr.context.operation, 'op'); + expect(tr.context.description, 'desc'); + expect(tr.startTimestamp.isAtSameMomentAs(startTime), true); + expect((tr as SentryTracer).name, 'name'); + expect(tr.origin, SentryTraceOrigins.manual); + }); + + test('start transaction binds span to the scope', () async { + final hub = fixture.getSut(); + + final tr = hub.startTransaction( + 'name', + 'op', + description: 'desc', + bindToScope: true, + ); + + await hub.configureScope((Scope scope) { + expect(scope.span, tr); + }); + }); + + test('start transaction does not bind span to the scope', () async { + final hub = fixture.getSut(); + + hub.startTransaction( + 'name', + 'op', + description: 'desc', + ); + + await hub.configureScope((Scope scope) { + expect(scope.span, isNull); + }); + }); + + test('start transaction samples the transaction', () async { + final hub = fixture.getSut(); + + final tr = hub.startTransaction( + 'name', + 'op', + description: 'desc', + ); + + expect(tr.samplingDecision?.sampled, true); + }); + + test('start transaction does not sample the transaction', () async { + final hub = fixture.getSut(tracesSampleRate: 0.0); + + final tr = hub.startTransaction( + 'name', + 'op', + description: 'desc', + ); + + expect(tr.samplingDecision?.sampled, false); + }); + + test('start transaction runs callback with customSamplingContext', + () async { + double? mySampling(SentrySamplingContext samplingContext) { + expect(samplingContext.customSamplingContext['test'], '1'); + return 0.0; + } + + final hub = fixture.getSut( + tracesSampleRate: null, + tracesSampler: mySampling, + ); + final map = {'test': '1'}; + + final tr = hub.startTransaction( + 'name', + 'op', + description: 'desc', + customSamplingContext: map, + ); + + expect(tr.samplingDecision?.sampled, false); + }); + + test('start transaction respects given sampled', () async { + final hub = fixture.getSut(); + + final tr = hub.startTransactionWithContext( + SentryTransactionContext('name', 'op', + samplingDecision: SentryTracesSamplingDecision(false)), + ); + + expect(tr.samplingDecision?.sampled, false); + }); + + test('start transaction with context sets trace origin fallback', () async { + final hub = fixture.getSut(); + final tr = hub.startTransactionWithContext( + SentryTransactionContext('name', 'op'), + ); + expect(tr.origin, SentryTraceOrigins.manual); + }); + + test('start transaction with context keeps origin', () async { + final hub = fixture.getSut(); + final tr = hub.startTransactionWithContext( + SentryTransactionContext('name', 'op', origin: 'auto.navigation.test'), + ); + expect(tr.origin, 'auto.navigation.test'); + }); + + test('start transaction return NoOp if performance is disabled', () async { + final hub = fixture.getSut(tracesSampleRate: null); + + final tr = hub.startTransaction( + 'name', + 'op', + description: 'desc', + ); + + expect(tr, NoOpSentrySpan()); + }); + + test('get span returns span bound to the scope', () async { + final hub = fixture.getSut(); + + final tr = hub.startTransaction( + 'name', + 'op', + description: 'desc', + bindToScope: true, + ); + + expect(hub.getSpan(), tr); + }); + + test('get span does not return span if not bound to the scope', () async { + final hub = fixture.getSut(); + + hub.startTransaction( + 'name', + 'op', + description: 'desc', + ); + + expect(hub.getSpan(), isNull); + }); + + test('get span does not return span if tracing is disabled', () async { + final hub = fixture.getSut(tracesSampleRate: null); + + hub.startTransaction( + 'name', + 'op', + description: 'desc', + ); + + expect(hub.getSpan(), isNull); + }); + + test('transaction isnt captured if not sampled', () async { + final hub = fixture.getSut(sampled: false); + + var tr = SentryTransaction(fixture.tracer); + final id = await hub.captureTransaction(tr); + + expect(id, SentryId.empty()); + }); + + test('transaction isnt captured if tracing is disabled', () async { + final hub = fixture.getSut(tracesSampleRate: null); + + var tr = SentryTransaction(fixture.tracer); + final id = await hub.captureTransaction(tr); + + expect(id, SentryId.empty()); + }); + + test('transaction is captured', () async { + final hub = fixture.getSut(); + + var tr = SentryTransaction(fixture.tracer); + final id = await hub.captureTransaction(tr); + + expect(id, tr.eventId); + expect(fixture.client.captureTransactionCalls.length, 1); + }); + + test('transaction is captured with traceContext', () async { + final hub = fixture.getSut(); + + var tr = SentryTransaction(fixture.tracer); + final context = SentryTraceContextHeader.fromJson({ + 'trace_id': '${tr.eventId}', + 'public_key': '123', + }); + final id = await hub.captureTransaction(tr, traceContext: context); + + expect(id, tr.eventId); + expect(fixture.client.captureTransactionCalls.length, 1); + expect( + fixture.client.captureTransactionCalls.first.traceContext, context); + }); + + test('captureTransaction hint is passed to client', () async { + final hub = fixture.getSut(); + + var hint = Hint(); + var tr = SentryTransaction(fixture.tracer); + await hub.captureTransaction(tr, hint: hint); + + expect(fixture.client.captureTransactionCalls.first.hint, hint); + }); + + test( + 'startTransactionWithContext sets traceId from scope propagationContext', + () async { + final hub = fixture.getSut(); + + hub.scope.propagationContext = PropagationContext(); + final tr1 = hub.startTransactionWithContext(fixture._context); + expect(tr1.traceContext()?.traceId, hub.scope.propagationContext.traceId); + + hub.scope.propagationContext = PropagationContext(); + final tr2 = hub.startTransactionWithContext(fixture._context); + expect(tr2.traceContext()?.traceId, hub.scope.propagationContext.traceId); + + expect(tr1.traceContext()?.traceId, isNot(tr2.traceContext()?.traceId)); + }); + }); + + group('Hub profiles', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('profiler is not started by default', () async { + final hub = fixture.getSut(); + final tr = hub.startTransaction('name', 'op'); + expect(tr, isA()); + expect((tr as SentryTracer).profiler, isNull); + }); + + test('profiler is started according to the sampling rate', () async { + final hub = fixture.getSut(); + final factory = MockSentryProfilerFactory(); + when(factory.startProfiler(fixture._context)) + .thenReturn(MockSentryProfiler()); + hub.profilerFactory = factory; + + var tr = hub.startTransactionWithContext(fixture._context); + expect((tr as SentryTracer).profiler, isNull); + verifyZeroInteractions(factory); + + hub.options.profilesSampleRate = 1.0; + tr = hub.startTransactionWithContext(fixture._context); + expect((tr as SentryTracer).profiler, isNotNull); + verify(factory.startProfiler(fixture._context)).called(1); + }); + + test('profiler.finish() is called', () async { + final hub = fixture.getSut(); + final factory = MockSentryProfilerFactory(); + final profiler = MockSentryProfiler(); + final expected = MockSentryProfileInfo(); + when(factory.startProfiler(fixture._context)).thenReturn(profiler); + when(profiler.finishFor(any)).thenAnswer((_) async => expected); + + hub.profilerFactory = factory; + hub.options.profilesSampleRate = 1.0; + final tr = hub.startTransactionWithContext(fixture._context); + await tr.finish(); + verify(profiler.finishFor(any)).called(1); + verify(profiler.dispose()).called(1); + }); + + test('profiler.dispose() is called even if not captured', () async { + final hub = fixture.getSut(); + final factory = MockSentryProfilerFactory(); + final profiler = MockSentryProfiler(); + final expected = MockSentryProfileInfo(); + when(factory.startProfiler(fixture._context)).thenReturn(profiler); + when(profiler.finishFor(any)).thenAnswer((_) async => expected); + + hub.profilerFactory = factory; + hub.options.profilesSampleRate = 1.0; + final tr = hub.startTransactionWithContext(fixture._context); + await tr.finish(status: SpanStatus.aborted()); + verify(profiler.dispose()).called(1); + verifyNever(profiler.finishFor(any)); + }); + }); + + group('Hub scope', () { + var hub = Hub(defaultTestOptions()); + var client = MockSentryClient(); + late SentryEvent fakeEvent; + late SentryUser fakeUser; + + setUp(() { + hub = Hub(defaultTestOptions()); + client = MockSentryClient(); + hub.bindClient(client); + fakeEvent = getFakeEvent(); + fakeUser = getFakeUser(); + }); + + test('returns scope', () async { + final scope = hub.scope; + expect(scope, isNotNull); + }); + + test('should configure its scope', () async { + await hub.configureScope((Scope scope) { + scope + ..level = SentryLevel.debug + ..fingerprint = ['1', '2']; + + scope.setUser(fakeUser); + }); + await hub.captureEvent(fakeEvent); + + expect(client.captureEventCalls.isNotEmpty, true); + expect(client.captureEventCalls.first.event, fakeEvent); + expect(client.captureEventCalls.first.scope, isNotNull); + final scope = client.captureEventCalls.first.scope; + + final otherScope = Scope(defaultTestOptions()) + ..level = SentryLevel.debug + ..fingerprint = ['1', '2']; + + await otherScope.setUser(fakeUser); + + expect( + scopeEquals( + scope, + otherScope, + ), + true, + ); + }); + + test('should configure scope async', () async { + await hub.configureScope((Scope scope) async { + await Future.delayed(Duration(milliseconds: 10)); + return scope.setUser(fakeUser); + }); + + await hub.captureEvent(fakeEvent); + + final scope = client.captureEventCalls.first.scope; + final otherScope = Scope(defaultTestOptions()); + await otherScope.setUser(fakeUser); + + expect( + scopeEquals( + scope, + otherScope, + ), + true); + }); + + test('should add breadcrumb to current Scope', () async { + await hub.configureScope((Scope scope) { + expect(0, scope.breadcrumbs.length); + }); + await hub.addBreadcrumb(Breadcrumb(message: 'test')); + await hub.configureScope((Scope scope) { + expect(1, scope.breadcrumbs.length); + expect('test', scope.breadcrumbs.first.message); + }); + }); + + test('generateNewTrace creates new trace id in propagation context', () { + final oldTraceId = hub.scope.propagationContext.traceId; + + hub.generateNewTrace(); + + final newTraceId = hub.scope.propagationContext.traceId; + expect(oldTraceId, isNot(newTraceId)); + }); + + test('generateNewTrace resets sampleRand in propagation context', () { + hub.scope.propagationContext.sampleRand = 1.0; + + hub.generateNewTrace(); + + final newSampleRand = hub.scope.propagationContext.sampleRand; + expect(newSampleRand, isNull); + }); + }); + + group('Hub scope callback', () { + late Fixture fixture; + late SentryEvent fakeEvent; + + setUp(() { + fixture = Fixture(); + fakeEvent = getFakeEvent(); + }); + + test('captureEvent should handle thrown error in scope callback', () async { + fixture.options.automatedTestMode = false; + final hub = fixture.getSut(debug: true); + final scopeCallbackException = Exception('error in scope callback'); + + ScopeCallback scopeCallback = (Scope scope) { + throw scopeCallbackException; + }; + + await hub.captureEvent(fakeEvent, withScope: scopeCallback); + + expect(fixture.loggedException, scopeCallbackException); + expect(fixture.loggedLevel, SentryLevel.error); + }); + + test('captureFeedback should handle thrown error in scope callback', + () async { + fixture.options.automatedTestMode = false; + final hub = fixture.getSut(debug: true); + final scopeCallbackException = Exception('error in scope callback'); + + ScopeCallback scopeCallback = (Scope scope) { + throw scopeCallbackException; + }; + + final feedback = SentryFeedback(message: 'message'); + await hub.captureFeedback(feedback, withScope: scopeCallback); + + expect(fixture.loggedException, scopeCallbackException); + expect(fixture.loggedLevel, SentryLevel.error); + }); + + test('captureException should handle thrown error in scope callback', + () async { + fixture.options.automatedTestMode = false; + final hub = fixture.getSut(debug: true); + final scopeCallbackException = Exception('error in scope callback'); + + ScopeCallback scopeCallback = (Scope scope) { + throw scopeCallbackException; + }; + + final exception = Exception("captured exception"); + await hub.captureException(exception, withScope: scopeCallback); + + expect(fixture.loggedException, scopeCallbackException); + expect(fixture.loggedLevel, SentryLevel.error); + }); + + test('captureMessage should handle thrown error in scope callback', + () async { + fixture.options.automatedTestMode = false; + final hub = fixture.getSut(debug: true); + final scopeCallbackException = Exception('error in scope callback'); + + ScopeCallback scopeCallback = (Scope scope) { + throw scopeCallbackException; + }; + + await hub.captureMessage("captured message", withScope: scopeCallback); + + expect(fixture.loggedException, scopeCallbackException); + expect(fixture.loggedLevel, SentryLevel.error); + }); + }); + + group('Hub Client', () { + late Hub hub; + late SentryClient client; + SentryOptions options; + late SentryEvent fakeEvent; + + setUp(() { + options = defaultTestOptions(); + fakeEvent = getFakeEvent(); + hub = Hub(options); + client = MockSentryClient(); + hub.bindClient(client); + }); + + test('should bind a new client', () async { + final client2 = MockSentryClient(); + hub.bindClient(client2); + await hub.captureEvent(fakeEvent); + expect(client2.captureEventCalls.length, 1); + expect(client2.captureEventCalls.first.event, fakeEvent); + expect(client2.captureEventCalls.first.scope, isNotNull); + }); + + test('should close its client', () async { + await hub.close(); + + expect(hub.isEnabled, false); + expect((client as MockSentryClient).closeCalls, 1); + }); + }); + + test('clones', () { + // TODO I'm not sure how to test it + // could we set [hub.stack] as @visibleForTesting ? + }); + + group('Hub withScope', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('captureEvent should create a new scope', () async { + final hub = fixture.getSut(); + await hub.captureEvent(SentryEvent()); + await hub.captureEvent(SentryEvent(), withScope: (scope) async { + await scope.setUser(SentryUser(id: 'foo bar')); + }); + await hub.captureEvent(SentryEvent()); + + var calls = fixture.client.captureEventCalls; + expect(calls.length, 3); + expect(calls[0].scope?.user, isNull); + expect(calls[1].scope?.user?.id, 'foo bar'); + expect(calls[2].scope?.user, isNull); + }); + + test('captureFeedback should create a new scope', () async { + final hub = fixture.getSut(); + await hub.captureFeedback(SentryFeedback(message: 'message')); + await hub.captureFeedback(SentryFeedback(message: 'message'), + withScope: (scope) async { + await scope.setUser(SentryUser(id: 'foo bar')); + }); + await hub.captureFeedback(SentryFeedback(message: 'message')); + + var calls = fixture.client.captureFeedbackCalls; + expect(calls.length, 3); + expect(calls[0].scope?.user, isNull); + expect(calls[1].scope?.user?.id, 'foo bar'); + expect(calls[2].scope?.user, isNull); + }); + + test('captureException should create a new scope', () async { + final hub = fixture.getSut(); + await hub.captureException(Exception('0')); + await hub.captureException(Exception('1'), withScope: (scope) async { + await scope.setUser(SentryUser(id: 'foo bar')); + }); + await hub.captureException(Exception('2')); + + var calls = fixture.client.captureEventCalls; + expect(calls.length, 3); + expect(calls[0].scope?.user, isNull); + expect(calls[0].event.throwable?.toString(), 'Exception: 0'); + + expect(calls[1].scope?.user?.id, 'foo bar'); + expect(calls[1].event.throwable?.toString(), 'Exception: 1'); + + expect(calls[2].scope?.user, isNull); + expect(calls[2].event.throwable?.toString(), 'Exception: 2'); + }); + + test('captureMessage should create a new scope', () async { + final hub = fixture.getSut(); + await hub.captureMessage('foo bar 0'); + await hub.captureMessage('foo bar 1', withScope: (scope) async { + await scope.setUser(SentryUser(id: 'foo bar')); + }); + await hub.captureMessage('foo bar 2'); + + var calls = fixture.client.captureMessageCalls; + expect(calls.length, 3); + expect(calls[0].scope?.user, isNull); + expect(calls[0].formatted, 'foo bar 0'); + + expect(calls[1].scope?.user?.id, 'foo bar'); + expect(calls[1].formatted, 'foo bar 1'); + + expect(calls[2].scope?.user, isNull); + expect(calls[2].formatted, 'foo bar 2'); + }); + + test( + 'withScope should use the same propagation context as the current scope', + () async { + final hub = fixture.getSut(); + late Scope clonedScope; + final currentScope = hub.scope; + await hub.captureEvent(SentryEvent(), withScope: (scope) async { + clonedScope = scope; + }); + + // Verify the propagation context is shared (same instance) + expect( + identical( + clonedScope.propagationContext, currentScope.propagationContext), + true, + reason: 'Propagation context should be the same instance'); + expect(clonedScope.propagationContext.traceId, + currentScope.propagationContext.traceId); + }); + }); + + group('ClientReportRecorder', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('record sample rate dropping transaction', () async { + final hub = fixture.getSut(sampled: false); + var transaction = SentryTransaction(fixture.tracer); + fixture.tracer.startChild('child1'); + fixture.tracer.startChild('child2'); + fixture.tracer.startChild('child3'); + + await hub.captureTransaction(transaction); + + expect(fixture.recorder.discardedEvents.length, 2); + + // we dropped the whole tracer and it has 3 span children so the span count should be 4 + // 3 children + 1 root span + final spanCount = fixture.recorder.discardedEvents + .firstWhere((element) => + element.category == DataCategory.span && + element.reason == DiscardReason.sampleRate) + .quantity; + expect(spanCount, 4); + }); + }); + + group('Hub Logs', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + SentryLog givenLog() { + return SentryLog( + timestamp: DateTime.now(), + traceId: SentryId.newId(), + level: SentryLogLevel.info, + body: 'test', + attributes: { + 'attribute': SentryLogAttribute.string('value'), + }, + ); + } + + test('captures logs', () async { + final hub = fixture.getSut(); + + final log = givenLog(); + await hub.captureLog(log); + + expect(fixture.client.captureLogCalls.length, 1); + expect(fixture.client.captureLogCalls.first.log, log); + }); + }); +} + +class Fixture { + final client = MockSentryClient(); + final recorder = MockClientReportRecorder(); + + final options = defaultTestOptions(); + late SentryTransactionContext _context; + late SentryTracer tracer; + + SentryLevel? loggedLevel; + String? loggedMessage; + Object? loggedException; + + Hub getSut({ + double? tracesSampleRate = 1.0, + TracesSamplerCallback? tracesSampler, + bool? sampled = true, + bool debug = false, + }) { + options.tracesSampleRate = tracesSampleRate; + options.tracesSampler = tracesSampler; + options.debug = debug; + options.log = mockLogger; // Enable logging in DiagnosticsLogger + + final hub = Hub(options); + + // A fully configured context - won't trigger a copy in startTransaction(). + _context = SentryTransactionContext( + 'name', + 'op', + samplingDecision: SentryTracesSamplingDecision(sampled!), + origin: SentryTraceOrigins.manual, + ); + + tracer = SentryTracer(_context, hub); + + hub.bindClient(client); + options.recorder = recorder; + + return hub; + } + + void mockLogger( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + loggedLevel = level; + loggedMessage = message; + loggedException = exception; + } +} diff --git a/packages/dart/test/initialization_test.dart b/packages/dart/test/initialization_test.dart new file mode 100644 index 0000000000..46c0cd35c6 --- /dev/null +++ b/packages/dart/test/initialization_test.dart @@ -0,0 +1,57 @@ +@TestOn('vm') +library; + +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; +import 'test_utils.dart'; + +// Tests for the following issue +// https://github.com/getsentry/sentry-dart/issues/508 +// There are no asserts, test are succesfull if no exceptions are thrown. +void main() { + tearDown(() async { + await Sentry.close(); + }); + + test('async re-initilization', () async { + final options = defaultTestOptions(); + await Sentry.init( + (options) { + options.dsn = fakeDsn; + }, + options: options, + ); + + await Sentry.close(); + + await Sentry.init( + (options) { + options.dsn = fakeDsn; + }, + options: options, + ); + }); + + // This is the failure from + // https://github.com/getsentry/sentry-dart/issues/508 + test('re-initilization', () async { + final options = defaultTestOptions(); + await Sentry.init( + (options) { + options.dsn = fakeDsn; + }, + options: options, + ); + + await Sentry.close(); + + await Sentry.init( + (options) { + options.dsn = fakeDsn; + }, + options: options, + ); + }); +} diff --git a/packages/dart/test/load_dart_debug_images_integration_test.dart b/packages/dart/test/load_dart_debug_images_integration_test.dart new file mode 100644 index 0000000000..87bc836cf7 --- /dev/null +++ b/packages/dart/test/load_dart_debug_images_integration_test.dart @@ -0,0 +1,310 @@ +@TestOn('vm') +library; + +import 'dart:async'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/load_dart_debug_images_integration.dart'; +import 'package:sentry/src/platform/mock_platform.dart'; +import 'package:sentry/src/sentry_stack_trace_factory.dart'; +import 'package:test/test.dart'; + +import 'mocks/mock_runtime_checker.dart'; +import 'test_utils.dart'; + +void main() { + final platforms = [ + MockPlatform.iOS(), + MockPlatform.macOS(), + MockPlatform.android(), + MockPlatform.windows(), + ]; + + for (final platform in platforms) { + group('$LoadDartDebugImagesIntegration $platform', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + fixture.options.platform = platform; + fixture.callIntegration(); + }); + + test('adds itself to sdk.integrations', () { + expect( + fixture.options.sdk.integrations + .contains(LoadDartDebugImagesIntegration.integrationName), + true, + ); + }); + + test('Event processor is added to options', () { + expect(fixture.options.eventProcessors.length, 1); + expect( + fixture.options.eventProcessors.first.runtimeType.toString(), + 'LoadDartDebugImagesIntegrationEventProcessor', + ); + }); + + test( + 'Event processor does not add debug image if symbolication is not needed', + () async { + final event = fixture.newEvent(needsSymbolication: false); + final resultEvent = await fixture.process(event); + + expect(resultEvent, equals(event)); + }); + + test('Event processor does not add debug image if stackTrace is null', + () async { + final event = fixture.newEvent(); + final resultEvent = await fixture.process(event); + + expect(resultEvent, equals(event)); + }); + + test( + 'Event processor does not add debug image if enableDartSymbolication is false', + () async { + fixture.options.enableDartSymbolication = false; + final event = fixture.newEvent(); + final resultEvent = await fixture.process(event); + + expect(resultEvent, equals(event)); + }); + + test('Event processor adds debug image when symbolication is needed', + () async { + final debugImage = await fixture.parseAndProcess(''' +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** +build_id: 'b680cb890f9e3c12a24b172d050dec73' +isolate_dso_base: 10000000 + #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 +'''); + expect(debugImage?.debugId, isNotEmpty); + expect(debugImage?.imageAddr, equals('0x10000000')); + }); + + test( + 'Event processor does not add debug image on second stack trace without image address', + () async { + final debugImage = await fixture.parseAndProcess(''' +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** +build_id: 'b680cb890f9e3c12a24b172d050dec73' +isolate_dso_base: 10000000 + #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 +'''); + expect(debugImage?.debugId, isNotEmpty); + expect(debugImage?.imageAddr, equals('0x10000000')); + + final event = fixture.newEvent(stackTrace: fixture.parse(''' +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** + #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 +''')); + final resultEvent = await fixture.process(event); + expect(resultEvent?.debugMeta?.images, isEmpty); + }); + + test('returns null for invalid stack trace', () async { + final event = + fixture.newEvent(stackTrace: fixture.parse('Invalid stack trace')); + final resultEvent = await fixture.process(event); + expect(resultEvent?.debugMeta?.images, isEmpty); + }); + + test('extracts correct debug ID with short debugId', () async { + final debugImage = await fixture.parseAndProcess(''' +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** +build_id: 'b680cb890f9e3c12a24b172d050dec73' +isolate_dso_base: 20000000 + #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 +'''); + + if (platform.isIOS || platform.isMacOS) { + expect(debugImage?.debugId, 'b680cb89-0f9e-3c12-a24b-172d050dec73'); + } else { + expect(debugImage?.debugId, '89cb80b6-9e0f-123c-a24b-172d050dec73'); + } + }); + + test('extracts correct debug ID for Android with long debugId', () async { + final debugImage = await fixture.parseAndProcess(''' +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** +build_id: 'f1c3bcc0279865fe3058404b2831d9e64135386c' +isolate_dso_base: 30000000 + #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 +'''); + + expect(debugImage?.debugId, + equals('c0bcc3f1-9827-fe65-3058-404b2831d9e6')); + }, skip: !platform.isAndroid); + + test('sets correct type based on platform', () async { + final debugImage = await fixture.parseAndProcess(''' +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** +build_id: 'b680cb890f9e3c12a24b172d050dec73' +isolate_dso_base: 40000000 + #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 +'''); + + if (platform.isAndroid || platform.isWindows) { + expect(debugImage?.type, 'elf'); + } else if (platform.isIOS || platform.isMacOS) { + expect(debugImage?.type, 'macho'); + } else { + fail('missing case for platform $platform'); + } + }); + + test('sets codeFile based on platform', () async { + final debugImage = await fixture.parseAndProcess(''' +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** +build_id: 'b680cb890f9e3c12a24b172d050dec73' +isolate_dso_base: 40000000 + #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 +'''); + + if (platform.isAndroid) { + expect(debugImage?.codeFile, 'libapp.so'); + } else if (platform.isWindows) { + expect(debugImage?.codeFile, 'data/app.so'); + } else if (platform.isIOS || platform.isMacOS) { + expect(debugImage?.codeFile, 'App.Framework/App'); + } else { + fail('missing case for platform $platform'); + } + }); + + test('debugImage is cached after first extraction', () async { + final stackTrace = ''' +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** +build_id: 'b680cb890f9e3c12a24b172d050dec73' +isolate_dso_base: 10000000 + #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 +'''; + // First extraction + final debugImage1 = await fixture.parseAndProcess(stackTrace); + expect(debugImage1, isNotNull); + + // Second extraction + final debugImage2 = await fixture.parseAndProcess(stackTrace); + expect(debugImage2, equals(debugImage1)); + }); + }); + } + + test('does add itself to sdk.integrations if split debug info is true', () { + final fixture = Fixture() + ..options.runtimeChecker = MockRuntimeChecker(isSplitDebugInfo: true); + fixture.callIntegration(); + expect( + fixture.options.sdk.integrations + .contains(LoadDartDebugImagesIntegration.integrationName), + isTrue, + ); + }); + + test('does add itself to sdk.integrations if obfuscation is true', () { + final fixture = Fixture() + ..options.runtimeChecker = MockRuntimeChecker(isObfuscated: true); + fixture.callIntegration(); + expect( + fixture.options.sdk.integrations + .contains(LoadDartDebugImagesIntegration.integrationName), + isTrue, + ); + }); + + test( + 'does not add itself to sdk.integrations if app obfuscation and split debug info is false', + () { + final fixture = Fixture()..options.runtimeChecker = MockRuntimeChecker(); + fixture.callIntegration(); + expect( + fixture.options.sdk.integrations + .contains(LoadDartDebugImagesIntegration.integrationName), + false, + ); + }); + + test('does add event processor to options if split debug info is true', () { + final fixture = Fixture() + ..options.runtimeChecker = MockRuntimeChecker(isSplitDebugInfo: true); + fixture.callIntegration(); + expect(fixture.options.eventProcessors.length, 1); + }); + + test('does add event processor to options if obfuscation is true', () { + final fixture = Fixture() + ..options.runtimeChecker = MockRuntimeChecker(isObfuscated: true); + fixture.callIntegration(); + expect(fixture.options.eventProcessors.length, 1); + }); + + test( + 'does not add event processor to options if app obfuscation and split debug info is false', + () { + final fixture = Fixture()..options.runtimeChecker = MockRuntimeChecker(); + fixture.callIntegration(); + expect(fixture.options.eventProcessors.length, 0); + }); + + test('does not add itself to sdk.integrations if platform is web', () { + final fixture = Fixture() + ..options.runtimeChecker = MockRuntimeChecker(isObfuscated: true) + ..options.platform = MockPlatform(isWeb: true); + fixture.callIntegration(); + expect( + fixture.options.sdk.integrations + .contains(LoadDartDebugImagesIntegration.integrationName), + false, + ); + }); + + test('debug image is null on unsupported platforms', () async { + final fixture = Fixture()..options.platform = MockPlatform.linux(); + fixture.callIntegration(); + final event = fixture.newEvent(stackTrace: fixture.parse(''' +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** +build_id: 'b680cb890f9e3c12a24b172d050dec73' +isolate_dso_base: 40000000 + #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 +''')); + final resultEvent = await fixture.process(event); + expect(resultEvent?.debugMeta?.images.length, 0); + }); +} + +class Fixture { + final options = defaultTestOptions() + ..runtimeChecker = + MockRuntimeChecker(isObfuscated: true, isSplitDebugInfo: true); + late final factory = SentryStackTraceFactory(options); + + void callIntegration() { + final integration = LoadDartDebugImagesIntegration(); + integration.call(Hub(options), options); + } + + SentryStackTrace parse(String stacktrace) => factory.parse(stacktrace); + + SentryEvent newEvent( + {bool needsSymbolication = true, SentryStackTrace? stackTrace}) { + stackTrace ??= SentryStackTrace(frames: [ + SentryStackFrame(platform: needsSymbolication ? null : 'dart') + ]); + return SentryEvent( + threads: [SentryThread(stacktrace: stackTrace)], + debugMeta: DebugMeta()); + } + + FutureOr process(SentryEvent event) => + options.eventProcessors.first.apply(event, Hint()); + + Future parseAndProcess(String stacktrace) async { + final event = newEvent(stackTrace: parse(stacktrace)); + final resultEvent = await process(event); + expect(resultEvent?.debugMeta?.images.length, 1); + return resultEvent?.debugMeta?.images.first; + } +} diff --git a/packages/dart/test/logs_enricher_interation_test.dart b/packages/dart/test/logs_enricher_interation_test.dart new file mode 100644 index 0000000000..78eda1b6a4 --- /dev/null +++ b/packages/dart/test/logs_enricher_interation_test.dart @@ -0,0 +1,99 @@ +@TestOn('vm') +library; + +import 'package:sentry/src/logs_enricher_integration.dart'; +import 'package:test/test.dart'; +import 'package:sentry/src/hub.dart'; +import 'package:sentry/src/protocol/sentry_log.dart'; +import 'package:sentry/src/protocol/sentry_log_attribute.dart'; +import 'package:sentry/src/protocol/sentry_id.dart'; +import 'package:sentry/src/protocol/sentry_log_level.dart'; +import 'test_utils.dart'; +import 'package:sentry/src/utils/os_utils.dart'; + +void main() { + SentryLog givenLog() { + return SentryLog( + timestamp: DateTime.now(), + traceId: SentryId.newId(), + level: SentryLogLevel.info, + body: 'test', + attributes: { + 'attribute': SentryLogAttribute.string('value'), + }, + ); + } + + group('LogsEnricherIntegration', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('adds itself to sdk.integrations if enableLogs is true', () { + fixture.options.enableLogs = true; + fixture.addIntegration(); + + expect( + fixture.options.sdk.integrations + .contains(LogsEnricherIntegration.integrationName), + true, + ); + }); + + test('does not add itself to sdk.integrations if enableLogs is false', () { + fixture.options.enableLogs = false; + fixture.addIntegration(); + + expect( + fixture.options.sdk.integrations + .contains(LogsEnricherIntegration.integrationName), + false, + ); + }); + + test( + 'adds os.name and os.version to log attributes on OnBeforeCaptureLog lifecycle event', + () async { + fixture.options.enableLogs = true; + fixture.addIntegration(); + + final log = givenLog(); + await fixture.hub.captureLog(log); + + final os = getSentryOperatingSystem(); + + expect(log.attributes['os.name']?.value, os.name); + expect(log.attributes['os.version']?.value, os.version); + }); + + test( + 'does not add os.name and os.version to log attributes if enableLogs is false', + () async { + fixture.options.enableLogs = false; + fixture.addIntegration(); + + final log = givenLog(); + await fixture.hub.captureLog(log); + + expect(log.attributes['os.name'], isNull); + expect(log.attributes['os.version'], isNull); + }); + }); +} + +class Fixture { + final options = defaultTestOptions(); + late final hub = Hub(options); + late final LogsEnricherIntegration integration; + + Fixture() { + options.enableLogs = true; + integration = LogsEnricherIntegration(); + } + + void addIntegration() { + integration.call(hub, options); + } +} diff --git a/packages/dart/test/mocks.dart b/packages/dart/test/mocks.dart new file mode 100644 index 0000000000..22900c657a --- /dev/null +++ b/packages/dart/test/mocks.dart @@ -0,0 +1,194 @@ +import 'package:mockito/annotations.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/profiling.dart'; +import 'package:sentry/src/transport/rate_limiter.dart'; + +final fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; + +final fakeException = Exception('Error'); + +SentryMessage getFakeMessage() { + return SentryMessage( + 'message 1', + template: 'message %d', + params: ['1'], + ); +} + +SentryUser getFakeUser() { + return SentryUser(id: '1', email: 'test@test'); +} + +SentryEvent getFakeEvent() { + return SentryEvent( + logger: 'main', + serverName: 'server.dart', + release: '1.4.0-preview.1', + environment: 'Test', + message: SentryMessage('This is an example Dart event.'), + transaction: '/example/app', + level: SentryLevel.warning, + tags: const {'project-id': '7371'}, + // ignore: deprecated_member_use_from_same_package + extra: const {'company-name': 'Dart Inc'}, + fingerprint: const ['example-dart'], + modules: const {'module1': 'factory'}, + sdk: SdkVersion(name: 'sdk1', version: '1.0.0'), + user: SentryUser( + id: '800', + username: 'first-user', + email: 'first@user.lan', + ipAddress: '127.0.0.1', + data: {'first-sign-in': '2020-01-01'}, + ), + breadcrumbs: [ + Breadcrumb( + message: 'UI Lifecycle', + timestamp: DateTime.now().toUtc(), + category: 'ui.lifecycle', + type: 'navigation', + data: {'screen': 'MainActivity', 'state': 'created'}, + level: SentryLevel.info, + ) + ], + contexts: Contexts( + operatingSystem: SentryOperatingSystem( + name: 'Android', + version: '5.0.2', + build: 'LRX22G.P900XXS0BPL2', + kernelVersion: + 'Linux version 3.4.39-5726670 (dpi@SWHC3807) (gcc version 4.8 (GCC) ) #1 SMP PREEMPT Thu Dec 1 19:42:39 KST 2016', + rooted: false, + ), + runtimes: [SentryRuntime(name: 'ART', version: '5')], + app: SentryApp( + name: 'Example Dart App', + version: '1.42.0', + identifier: 'HGT-App-13', + build: '93785', + buildType: 'release', + deviceAppHash: '5afd3a6', + startTime: DateTime.now().toUtc(), + ), + browser: SentryBrowser( + name: 'Firefox', + version: '42.0.1', + ), + device: SentryDevice( + name: 'SM-P900', + family: 'SM-P900', + model: 'SM-P900 (LRX22G)', + modelId: 'LRX22G', + arch: 'armeabi-v7a', + batteryLevel: 99, + orientation: SentryOrientation.landscape, + manufacturer: 'samsung', + brand: 'samsung', + screenDensity: 2.1, + screenDpi: 320, + online: true, + charging: true, + lowMemory: true, + simulator: false, + memorySize: 1500, + freeMemory: 200, + usableMemory: 4294967296, + storageSize: 4294967296, + freeStorage: 2147483648, + externalStorageSize: 8589934592, + externalFreeStorage: 2863311530, + bootTime: DateTime.now().toUtc(), + ), + ), + ); +} + +/// Always returns null and thus drops all events +class DropAllEventProcessor implements EventProcessor { + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + return null; + } +} + +class DropSpansEventProcessor implements EventProcessor { + DropSpansEventProcessor(this.numberOfSpansToDrop); + + final int numberOfSpansToDrop; + + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + if (event is SentryTransaction) { + if (numberOfSpansToDrop > event.spans.length) { + throw ArgumentError( + 'numberOfSpansToDrop must be less than the number of spans in the transaction'); + } + final droppedSpans = event.spans.take(numberOfSpansToDrop).toList(); + event.spans.removeWhere((element) => droppedSpans.contains(element)); + } + return event; + } +} + +class FunctionEventProcessor implements EventProcessor { + FunctionEventProcessor(this.applyFunction); + + final EventProcessorFunction applyFunction; + + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + return applyFunction(event, hint); + } +} + +typedef EventProcessorFunction = SentryEvent? Function( + SentryEvent event, Hint hint); + +SentryEnvelope getFakeEnvelope() { + return SentryEnvelope.fromEvent( + getFakeEvent(), + SdkVersion(name: 'sdk1', version: '1.0.0'), + dsn: fakeDsn, + ); +} + +class MockRateLimiter implements RateLimiter { + bool filterReturnsNull = false; + SentryEnvelope? filteredEnvelope; + SentryEnvelope? envelopeToFilter; + + String? sentryRateLimitHeader; + String? retryAfterHeader; + int? errorCode; + + @override + SentryEnvelope? filter(SentryEnvelope envelope) { + if (filterReturnsNull) { + return null; + } + envelopeToFilter = envelope; + return filteredEnvelope ?? envelope; + } + + @override + void updateRetryAfterLimits( + String? sentryRateLimitHeader, String? retryAfterHeader, int errorCode) { + this.sentryRateLimitHeader = sentryRateLimitHeader; + this.retryAfterHeader = retryAfterHeader; + this.errorCode = errorCode; + } +} + +final Map testUnknown = { + 'unknown-string': 'foo', + 'unknown-bool': true, + 'unknown-num': 9001, +}; + +@GenerateMocks([ + SentryProfilerFactory, + SentryProfiler, + SentryProfileInfo, + ExceptionTypeIdentifier, +]) +void main() {} diff --git a/packages/dart/test/mocks.mocks.dart b/packages/dart/test/mocks.mocks.dart new file mode 100644 index 0000000000..efc78832af --- /dev/null +++ b/packages/dart/test/mocks.mocks.dart @@ -0,0 +1,115 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in sentry/test/mocks.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:sentry/sentry.dart' as _i2; +import 'package:sentry/src/profiling.dart' as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeSentryEnvelopeItem_0 extends _i1.SmartFake + implements _i2.SentryEnvelopeItem { + _FakeSentryEnvelopeItem_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [SentryProfilerFactory]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockSentryProfilerFactory extends _i1.Mock + implements _i3.SentryProfilerFactory { + MockSentryProfilerFactory() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.SentryProfiler? startProfiler(_i2.SentryTransactionContext? context) => + (super.noSuchMethod(Invocation.method( + #startProfiler, + [context], + )) as _i3.SentryProfiler?); +} + +/// A class which mocks [SentryProfiler]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockSentryProfiler extends _i1.Mock implements _i3.SentryProfiler { + MockSentryProfiler() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Future<_i3.SentryProfileInfo?> finishFor( + _i2.SentryTransaction? transaction) => + (super.noSuchMethod( + Invocation.method( + #finishFor, + [transaction], + ), + returnValue: _i4.Future<_i3.SentryProfileInfo?>.value(), + ) as _i4.Future<_i3.SentryProfileInfo?>); + + @override + void dispose() => super.noSuchMethod( + Invocation.method( + #dispose, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [SentryProfileInfo]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockSentryProfileInfo extends _i1.Mock implements _i3.SentryProfileInfo { + MockSentryProfileInfo() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.SentryEnvelopeItem asEnvelopeItem() => (super.noSuchMethod( + Invocation.method( + #asEnvelopeItem, + [], + ), + returnValue: _FakeSentryEnvelopeItem_0( + this, + Invocation.method( + #asEnvelopeItem, + [], + ), + ), + ) as _i2.SentryEnvelopeItem); +} + +/// A class which mocks [ExceptionTypeIdentifier]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockExceptionTypeIdentifier extends _i1.Mock + implements _i2.ExceptionTypeIdentifier { + MockExceptionTypeIdentifier() { + _i1.throwOnMissingStub(this); + } +} diff --git a/packages/dart/test/mocks/mock_client_report_recorder.dart b/packages/dart/test/mocks/mock_client_report_recorder.dart new file mode 100644 index 0000000000..4d8eaa5b1d --- /dev/null +++ b/packages/dart/test/mocks/mock_client_report_recorder.dart @@ -0,0 +1,25 @@ +import 'package:sentry/src/client_reports/client_report_recorder.dart'; +import 'package:sentry/src/client_reports/discard_reason.dart'; +import 'package:sentry/src/client_reports/client_report.dart'; +import 'package:sentry/src/client_reports/discarded_event.dart'; +import 'package:sentry/src/transport/data_category.dart'; + +class MockClientReportRecorder implements ClientReportRecorder { + List discardedEvents = []; + + ClientReport? clientReport; + + bool flushCalled = false; + + @override + ClientReport? flush() { + flushCalled = true; + return clientReport; + } + + @override + void recordLostEvent(DiscardReason reason, DataCategory category, + {int count = 1}) { + discardedEvents.add(DiscardedEvent(reason, category, count)); + } +} diff --git a/packages/dart/test/mocks/mock_envelope.dart b/packages/dart/test/mocks/mock_envelope.dart new file mode 100644 index 0000000000..1009f2e396 --- /dev/null +++ b/packages/dart/test/mocks/mock_envelope.dart @@ -0,0 +1,29 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_envelope_header.dart'; +import 'package:sentry/src/client_reports/client_report.dart'; + +class MockEnvelope implements SentryEnvelope { + ClientReport? clientReport; + + @override + void addClientReport(ClientReport? clientReport) { + this.clientReport = clientReport; + } + + @override + Stream> envelopeStream(SentryOptions options) async* { + yield [0]; + } + + @override + SentryEnvelopeHeader get header => SentryEnvelopeHeader( + SentryId.empty(), + SdkVersion(name: 'fixture-name', version: '1'), + ); + + @override + List items = []; + + @override + bool get containsUnhandledException => false; +} diff --git a/packages/dart/test/mocks/mock_environment_variables.dart b/packages/dart/test/mocks/mock_environment_variables.dart new file mode 100644 index 0000000000..b7a60a3f31 --- /dev/null +++ b/packages/dart/test/mocks/mock_environment_variables.dart @@ -0,0 +1,33 @@ +import 'package:sentry/src/environment/environment_variables.dart'; + +import 'no_such_method_provider.dart'; + +class MockEnvironmentVariables extends EnvironmentVariables + with NoSuchMethodProvider { + MockEnvironmentVariables({ + String? dist, + String? dsn, + String? environment, + String? release, + }) : _dist = dist, + _dsn = dsn, + _environment = environment, + _release = release; + + final String? _dist; + final String? _dsn; + final String? _environment; + final String? _release; + + @override + String? get dist => _dist; + + @override + String? get dsn => _dsn; + + @override + String? get environment => _environment; + + @override + String? get release => _release; +} diff --git a/packages/dart/test/mocks/mock_hub.dart b/packages/dart/test/mocks/mock_hub.dart new file mode 100644 index 0000000000..6b8d530cae --- /dev/null +++ b/packages/dart/test/mocks/mock_hub.dart @@ -0,0 +1,196 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:sentry/sentry.dart'; + +import '../test_utils.dart'; +import 'mock_sentry_client.dart'; +import 'no_such_method_provider.dart'; + +class MockHub with NoSuchMethodProvider implements Hub { + List captureEventCalls = []; + List captureExceptionCalls = []; + List captureMessageCalls = []; + List addBreadcrumbCalls = []; + List captureLogCalls = []; + List bindClientCalls = []; + + // ignore: deprecated_member_use_from_same_package + List captureTransactionCalls = []; + int closeCalls = 0; + bool _isEnabled = true; + int spanContextCals = 0; + int getSpanCalls = 0; + + final _options = defaultTestOptions(); + + late Scope _scope; + + @override + @internal + SentryOptions get options => _options; + + MockHub() { + _scope = Scope(_options); + } + + /// Useful for tests. + void reset() { + captureEventCalls = []; + captureExceptionCalls = []; + captureMessageCalls = []; + addBreadcrumbCalls = []; + bindClientCalls = []; + closeCalls = 0; + _isEnabled = true; + spanContextCals = 0; + captureTransactionCalls = []; + getSpanCalls = 0; + _scope = Scope(_options); + } + + @override + Future addBreadcrumb(Breadcrumb crumb, {Hint? hint}) async { + addBreadcrumbCalls.add(AddBreadcrumbCall(crumb, hint)); + } + + @override + void bindClient(SentryClient client) { + bindClientCalls.add(client); + } + + @override + Future captureEvent( + SentryEvent event, { + dynamic stackTrace, + Hint? hint, + ScopeCallback? withScope, + }) async { + captureEventCalls.add(CaptureEventCall( + event, + stackTrace, + hint, + )); + return event.eventId; + } + + @override + Future captureException( + dynamic throwable, { + dynamic stackTrace, + Hint? hint, + SentryMessage? message, + ScopeCallback? withScope, + }) async { + captureExceptionCalls.add(CaptureExceptionCall( + throwable, + stackTrace, + hint, + message, + )); + return SentryId.newId(); + } + + @override + Future captureMessage( + String? message, { + SentryLevel? level = SentryLevel.info, + String? template, + List? params, + Hint? hint, + ScopeCallback? withScope, + }) async { + captureMessageCalls.add(CaptureMessageCall( + message, + level, + template, + params, + hint, + )); + return SentryId.newId(); + } + + @override + FutureOr captureLog(SentryLog log) async { + captureLogCalls.add(CaptureLogCall(log, null)); + } + + @override + Future close() async { + closeCalls = closeCalls + 1; + _isEnabled = false; + } + + @override + bool get isEnabled => _isEnabled; + + @override + Future captureTransaction( + SentryTransaction transaction, { + SentryTraceContextHeader? traceContext, + Hint? hint, + }) async { + captureTransactionCalls + .add(CaptureTransactionCall(transaction, traceContext, hint)); + return transaction.eventId; + } + + @override + ISentrySpan? getSpan() { + getSpanCalls++; + return null; + } + + @override + void setSpanContext(throwable, ISentrySpan span, String transaction) { + spanContextCals++; + } + + @override + Scope get scope => _scope; +} + +class CaptureEventCall { + final SentryEvent event; + final dynamic stackTrace; + final Hint? hint; + + CaptureEventCall(this.event, this.stackTrace, this.hint); +} + +class CaptureExceptionCall { + final dynamic throwable; + final dynamic stackTrace; + final Hint? hint; + final SentryMessage? message; + + CaptureExceptionCall( + this.throwable, + this.stackTrace, + this.hint, + this.message, + ); +} + +class CaptureMessageCall { + final String? message; + final SentryLevel? level; + final String? template; + final List? params; + final Hint? hint; + + CaptureMessageCall( + this.message, + this.level, + this.template, + this.params, + this.hint, + ); +} + +class AddBreadcrumbCall { + final Breadcrumb crumb; + final Hint? hint; + + AddBreadcrumbCall(this.crumb, this.hint); +} diff --git a/packages/dart/test/mocks/mock_integration.dart b/packages/dart/test/mocks/mock_integration.dart new file mode 100644 index 0000000000..c9945ed6db --- /dev/null +++ b/packages/dart/test/mocks/mock_integration.dart @@ -0,0 +1,20 @@ +import 'package:sentry/sentry.dart'; + +import 'no_such_method_provider.dart'; + +class MockIntegration + with NoSuchMethodProvider + implements Integration { + int closeCalls = 0; + int callCalls = 0; + + @override + void call(Hub hub, SentryOptions options) { + callCalls = callCalls + 1; + } + + @override + void close() { + closeCalls = closeCalls + 1; + } +} diff --git a/packages/dart/test/mocks/mock_log_batcher.dart b/packages/dart/test/mocks/mock_log_batcher.dart new file mode 100644 index 0000000000..9de9a8ae5d --- /dev/null +++ b/packages/dart/test/mocks/mock_log_batcher.dart @@ -0,0 +1,19 @@ +import 'dart:async'; + +import 'package:sentry/src/protocol/sentry_log.dart'; +import 'package:sentry/src/sentry_log_batcher.dart'; + +class MockLogBatcher implements SentryLogBatcher { + final addLogCalls = []; + final flushCalls = []; + + @override + FutureOr addLog(SentryLog log) { + addLogCalls.add(log); + } + + @override + Future flush() async { + flushCalls.add(null); + } +} diff --git a/packages/dart/test/mocks/mock_runtime_checker.dart b/packages/dart/test/mocks/mock_runtime_checker.dart new file mode 100644 index 0000000000..ae1885963a --- /dev/null +++ b/packages/dart/test/mocks/mock_runtime_checker.dart @@ -0,0 +1,35 @@ +import 'package:sentry/src/runtime_checker.dart'; + +import 'no_such_method_provider.dart'; + +class MockRuntimeChecker extends RuntimeChecker with NoSuchMethodProvider { + MockRuntimeChecker({ + this.isDebug = false, + this.isProfile = false, + this.isRelease = false, + this.isObfuscated = false, + this.isSplitDebugInfo = false, + bool isRootZone = true, + }) : super(isRootZone: isRootZone); + + final bool isDebug; + final bool isProfile; + final bool isRelease; + final bool isObfuscated; + final bool isSplitDebugInfo; + + @override + bool isDebugMode() => isDebug; + + @override + bool isProfileMode() => isProfile; + + @override + bool isReleaseMode() => isRelease; + + @override + bool isAppObfuscated() => isObfuscated; + + @override + bool isSplitDebugInfoBuild() => isSplitDebugInfo; +} diff --git a/packages/dart/test/mocks/mock_scope_observer.dart b/packages/dart/test/mocks/mock_scope_observer.dart new file mode 100644 index 0000000000..d0d5d8c048 --- /dev/null +++ b/packages/dart/test/mocks/mock_scope_observer.dart @@ -0,0 +1,79 @@ +import 'package:sentry/sentry.dart'; + +class MockScopeObserver extends ScopeObserver { + List addedBreadcrumbs = []; + bool calledAddBreadcrumb = false; + bool calledClearBreadcrumbs = false; + bool calledRemoveContexts = false; + bool calledRemoveExtra = false; + bool calledRemoveTag = false; + bool calledSetContexts = false; + bool calledSetExtra = false; + bool calledSetTag = false; + bool calledSetUser = false; + + int numberOfAddBreadcrumbCalls = 0; + int numberOfClearBreadcrumbsCalls = 0; + int numberOfRemoveContextsCalls = 0; + int numberOfRemoveExtraCalls = 0; + int numberOfRemoveTagCalls = 0; + int numberOfSetContextsCalls = 0; + int numberOfSetExtraCalls = 0; + int numberOfSetTagCalls = 0; + int numberOfSetUserCalls = 0; + + @override + Future addBreadcrumb(Breadcrumb breadcrumb) async { + calledAddBreadcrumb = true; + numberOfAddBreadcrumbCalls += 1; + addedBreadcrumbs.add(breadcrumb); + } + + @override + Future clearBreadcrumbs() async { + calledClearBreadcrumbs = true; + numberOfClearBreadcrumbsCalls += 1; + } + + @override + Future removeContexts(String key) async { + calledRemoveContexts = true; + numberOfRemoveContextsCalls += 1; + } + + @override + Future removeExtra(String key) async { + calledRemoveExtra = true; + numberOfRemoveExtraCalls += 1; + } + + @override + Future removeTag(String key) async { + calledRemoveTag = true; + numberOfRemoveTagCalls += 1; + } + + @override + Future setContexts(String key, value) async { + calledSetContexts = true; + numberOfSetContextsCalls += 1; + } + + @override + Future setExtra(String key, value) async { + calledSetExtra = true; + numberOfSetExtraCalls += 1; + } + + @override + Future setTag(String key, String value) async { + calledSetTag = true; + numberOfSetTagCalls += 1; + } + + @override + Future setUser(SentryUser? user) async { + calledSetUser = true; + numberOfSetUserCalls += 1; + } +} diff --git a/packages/dart/test/mocks/mock_sentry_client.dart b/packages/dart/test/mocks/mock_sentry_client.dart new file mode 100644 index 0000000000..00a61cc203 --- /dev/null +++ b/packages/dart/test/mocks/mock_sentry_client.dart @@ -0,0 +1,188 @@ +import 'dart:async'; +import 'package:sentry/sentry.dart'; + +import 'no_such_method_provider.dart'; + +class MockSentryClient with NoSuchMethodProvider implements SentryClient { + List captureEventCalls = []; + List captureExceptionCalls = []; + List captureMessageCalls = []; + List captureEnvelopeCalls = []; + List captureTransactionCalls = []; + List captureFeedbackCalls = []; + List captureLogCalls = []; + int closeCalls = 0; + + @override + Future captureEvent( + SentryEvent event, { + Scope? scope, + dynamic stackTrace, + Hint? hint, + }) async { + captureEventCalls.add(CaptureEventCall( + event, + scope, + stackTrace, + hint, + )); + return event.eventId; + } + + @override + Future captureException( + dynamic throwable, { + dynamic stackTrace, + Scope? scope, + Hint? hint, + }) async { + captureExceptionCalls.add(CaptureExceptionCall( + throwable, + stackTrace, + scope, + hint, + )); + return SentryId.newId(); + } + + @override + Future captureMessage( + String? formatted, { + SentryLevel? level = SentryLevel.info, + String? template, + List? params, + Scope? scope, + Hint? hint, + }) async { + captureMessageCalls.add(CaptureMessageCall( + formatted, + level, + template, + params, + scope, + hint, + )); + return SentryId.newId(); + } + + @override + Future captureEnvelope(SentryEnvelope envelope) async { + captureEnvelopeCalls.add(CaptureEnvelopeCall(envelope)); + return envelope.header.eventId ?? SentryId.newId(); + } + + @override + Future captureFeedback( + SentryFeedback feedback, { + Scope? scope, + Hint? hint, + }) async { + captureFeedbackCalls.add(CaptureFeedbackCall( + feedback, + scope, + hint, + )); + return SentryId.newId(); + } + + @override + FutureOr captureLog(SentryLog log, {Scope? scope}) async { + captureLogCalls.add(CaptureLogCall(log, scope)); + } + + @override + void close() { + closeCalls = closeCalls + 1; + } + + @override + Future captureTransaction( + SentryTransaction transaction, { + Scope? scope, + SentryTraceContextHeader? traceContext, + Hint? hint, + }) async { + captureTransactionCalls + .add(CaptureTransactionCall(transaction, traceContext, hint)); + return transaction.eventId; + } +} + +class CaptureEventCall { + final SentryEvent event; + final Scope? scope; + final dynamic stackTrace; + final Hint? hint; + + CaptureEventCall( + this.event, + this.scope, + this.stackTrace, + this.hint, + ); +} + +class CaptureFeedbackCall { + final SentryFeedback feedback; + final Hint? hint; + final Scope? scope; + + CaptureFeedbackCall( + this.feedback, + this.scope, + this.hint, + ); +} + +class CaptureExceptionCall { + final dynamic throwable; + final dynamic stackTrace; + final Scope? scope; + final Hint? hint; + + CaptureExceptionCall( + this.throwable, + this.stackTrace, + this.scope, + this.hint, + ); +} + +class CaptureMessageCall { + final String? formatted; + final SentryLevel? level; + final String? template; + final List? params; + final Scope? scope; + final Hint? hint; + + CaptureMessageCall( + this.formatted, + this.level, + this.template, + this.params, + this.scope, + this.hint, + ); +} + +class CaptureEnvelopeCall { + final SentryEnvelope envelope; + + CaptureEnvelopeCall(this.envelope); +} + +class CaptureTransactionCall { + final SentryTransaction transaction; + final SentryTraceContextHeader? traceContext; + final Hint? hint; + + CaptureTransactionCall(this.transaction, this.traceContext, this.hint); +} + +class CaptureLogCall { + final SentryLog log; + final Scope? scope; + + CaptureLogCall(this.log, this.scope); +} diff --git a/packages/dart/test/mocks/mock_transport.dart b/packages/dart/test/mocks/mock_transport.dart new file mode 100644 index 0000000000..f9ba5b4829 --- /dev/null +++ b/packages/dart/test/mocks/mock_transport.dart @@ -0,0 +1,78 @@ +import 'dart:convert'; + +import 'package:sentry/sentry.dart'; +import 'package:test/expect.dart'; + +class MockTransport implements Transport { + List envelopes = []; + List events = []; + List statsdItems = []; + List> logs = []; + + int _calls = 0; + String _exceptions = ''; + + int get calls { + expect(_exceptions, isEmpty); + return _calls; + } + + bool parseFromEnvelope = true; + + bool called(int calls) { + return _calls == calls; + } + + @override + Future send(SentryEnvelope envelope) async { + _calls++; + + // Exception here would be swallowed by Sentry, making it hard to find test + // failure causes. Instead, we log them and check on access to [calls]. + try { + envelopes.add(envelope); + if (parseFromEnvelope) { + await _parseEnvelope(envelope); + } + } catch (e, stack) { + _exceptions += '$e\n$stack\n\n'; + rethrow; + } + + return envelope.header.eventId ?? SentryId.empty(); + } + + Future _parseEnvelope(SentryEnvelope envelope) async { + final RegExp statSdRegex = RegExp('^(?!{).+@.+:.+\\|.+', multiLine: true); + + final envelopeItemData = await envelope.items.first.dataFactory(); + final envelopeItem = utf8.decode(envelopeItemData); + + if (statSdRegex.hasMatch(envelopeItem)) { + statsdItems.add(envelopeItem); + } else if (envelopeItem.contains('items') && + envelopeItem.contains('timestamp') && + envelopeItem.contains('trace_id') && + envelopeItem.contains('level') && + envelopeItem.contains('body')) { + final envelopeItemJson = jsonDecode(envelopeItem) as Map; + logs.add(envelopeItemJson); + } else { + final envelopeItemJson = jsonDecode(envelopeItem) as Map; + events.add(SentryEvent.fromJson(envelopeItemJson)); + } + } + + void reset() { + envelopes.clear(); + events.clear(); + _calls = 0; + } +} + +class ThrowingTransport implements Transport { + @override + Future send(SentryEnvelope envelope) async { + throw Exception('foo bar'); + } +} diff --git a/packages/dart/test/mocks/no_such_method_provider.dart b/packages/dart/test/mocks/no_such_method_provider.dart new file mode 100644 index 0000000000..64253e9651 --- /dev/null +++ b/packages/dart/test/mocks/no_such_method_provider.dart @@ -0,0 +1,7 @@ +mixin NoSuchMethodProvider { + @override + void noSuchMethod(Invocation invocation) { + 'Method ${invocation.memberName} was called ' + 'with arguments ${invocation.positionalArguments}'; + } +} diff --git a/packages/dart/test/propagation_context_test.dart b/packages/dart/test/propagation_context_test.dart new file mode 100644 index 0000000000..22347faa3a --- /dev/null +++ b/packages/dart/test/propagation_context_test.dart @@ -0,0 +1,157 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:test/test.dart'; + +import 'test_utils.dart'; + +void main() { + group('PropagationContext', () { + group('traceId', () { + test('is a new trace id by default', () { + final hub = Hub(defaultTestOptions()); + final sut = hub.scope.propagationContext; + final traceId = sut.traceId; + expect(traceId, isNotNull); + }); + + test('is reused for transactions within the same trace', () { + final options = defaultTestOptions()..tracesSampleRate = 1.0; + final hub = Hub(options); + final sut = hub.scope.propagationContext; + + final tx1 = hub.startTransaction('tx1', 'op') as SentryTracer; + final traceId1 = sut.traceId; + + final tx2 = hub.startTransaction('tx2', 'op') as SentryTracer; + final traceId2 = sut.traceId; + + expect(tx1.context.traceId, equals(tx2.context.traceId)); + expect(tx1.context.traceId, equals(traceId1)); + expect(traceId1, equals(traceId2)); + }); + }); + + group('sampleRand', () { + test('is null by default', () { + final hub = Hub(defaultTestOptions()); + final sut = hub.scope.propagationContext; + final sampleRand = sut.sampleRand; + expect(sampleRand, isNull); + }); + + test('is set by the first transaction and stays unchanged', () { + final options = defaultTestOptions()..tracesSampleRate = 1.0; + final hub = Hub(options); + final sut = hub.scope.propagationContext; + + final tx1 = hub.startTransaction('tx1', 'op') as SentryTracer; + final rand1 = tx1.samplingDecision?.sampleRand; + expect(rand1, isNotNull); + + final tx2 = hub.startTransaction('tx2', 'op') as SentryTracer; + final rand2 = tx2.samplingDecision?.sampleRand; + + expect(rand2, equals(rand1)); + expect(rand1, equals(sut.sampleRand)); + }); + }); + + group('sampled', () { + test('is null by default', () { + final hub = Hub(defaultTestOptions()..tracesSampleRate = 1.0); + final sut = hub.scope.propagationContext; + expect(sut.sampled, isNull); + }); + + test('is set by the first transaction and stays unchanged', () { + final hub = Hub(defaultTestOptions()..tracesSampleRate = 1.0); + final sut = hub.scope.propagationContext; + // 1. Start the first (root) transaction with an explicit sampled = true. + final txContextTrue = SentryTransactionContext( + 'trx', + 'op', + samplingDecision: SentryTracesSamplingDecision(true), + ); + hub.startTransactionWithContext(txContextTrue); + + expect(sut.sampled, isTrue); + + // 2. Start a second transaction with sampled = false – the flag must not change. + final txContextFalse = SentryTransactionContext( + 'trx-2', + 'op', + samplingDecision: SentryTracesSamplingDecision(false), + ); + hub.startTransactionWithContext(txContextFalse); + + expect(sut.sampled, isTrue, + reason: 'sampled flag must remain unchanged for the trace'); + }); + + test('is reset when a new trace is generated', () { + final hub = Hub(defaultTestOptions()..tracesSampleRate = 1.0); + final sut = hub.scope.propagationContext; + final txContext = SentryTransactionContext( + 'trx', + 'op', + samplingDecision: SentryTracesSamplingDecision(true), + ); + hub.startTransactionWithContext(txContext); + expect(sut.sampled, isTrue); + + // Simulate new trace. + hub.generateNewTrace(); + expect(sut.sampled, isNull); + }); + + test('applySamplingDecision only sets sampled flag once', () { + final hub = Hub(defaultTestOptions()..tracesSampleRate = 1.0); + final sut = hub.scope.propagationContext; + + expect(sut.sampled, isNull); + sut.applySamplingDecision(true); + expect(sut.sampled, isTrue); + sut.applySamplingDecision(false); + expect(sut.sampled, isTrue); + }); + }); + + group('resetTrace', () { + test('resets values', () { + final hub = Hub(defaultTestOptions()..tracesSampleRate = 1.0); + final sut = hub.scope.propagationContext; + + final traceId = SentryId.newId(); + sut.traceId = traceId; + sut.sampleRand = 1.0; + sut.applySamplingDecision(true); + + sut.resetTrace(); + + expect(sut.traceId, isNot(traceId)); + expect(sut.sampleRand, isNull); + expect(sut.sampled, isNull); + }); + }); + + group('toSentryTrace', () { + test('header reflects values', () { + final options = defaultTestOptions()..tracesSampleRate = 1.0; + final hub = Hub(options); + final sut = hub.scope.propagationContext; + + final txContext = SentryTransactionContext( + 'trx', + 'op', + samplingDecision: SentryTracesSamplingDecision(true), + ); + hub.startTransactionWithContext(txContext); + + final header = sut.toSentryTrace(); + expect(header.sampled, isTrue); + expect(header.value.split('-').length, 3, + reason: 'header must contain the sampled decision'); + }); + }); + }); +} diff --git a/packages/dart/test/protocol/access_aware_map_tests.dart b/packages/dart/test/protocol/access_aware_map_tests.dart new file mode 100644 index 0000000000..b9c08f2b9a --- /dev/null +++ b/packages/dart/test/protocol/access_aware_map_tests.dart @@ -0,0 +1,118 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/src/protocol/access_aware_map.dart'; +import 'package:test/test.dart'; + +void main() { + group('MapBase', () { + test('set/get value for key', () { + final sut = AccessAwareMap({ + 'foo': 'foo', + }); + + sut['foo'] = 'bar'; + sut['bar'] = 'foo'; + + expect(sut['foo'], 'bar'); + expect(sut['bar'], 'foo'); + }); + + test('clear', () { + final sut = AccessAwareMap({ + 'foo': 'foo', + }); + + sut.clear(); + + expect(sut.isEmpty, true); + }); + + test('keys', () { + final sut = AccessAwareMap({ + 'foo': 'foo', + 'bar': 'bar', + }); + expect( + sut.keys.sortedBy((it) => it), ['bar', 'foo'].sortedBy((it) => it)); + }); + + test('remove', () { + final sut = AccessAwareMap({ + 'foo': 'foo', + }); + + sut.remove('foo'); + + expect(sut.isEmpty, true); + }); + }); + + group('access aware', () { + test('collects accessedKeys', () { + final sut = AccessAwareMap({ + 'foo': 'foo', + 'bar': 'bar', + }); + + sut['foo']; + sut['bar']; + sut['baz']; + + expect(sut.accessedKeysWithValues, {'foo', 'bar', 'baz'}); + }); + + test('returns notAccessed data', () { + final sut = AccessAwareMap({ + 'foo': 'foo', + 'bar': 'bar', + }); + + sut['foo']; + + final notAccessed = sut.notAccessed(); + expect(notAccessed, isNotNull); + expect(notAccessed?.containsKey('foo'), false); + expect(notAccessed?.containsKey('bar'), true); + }); + }); + + group('map base functionality', () { + test('set value with []= operator', () { + final sut = AccessAwareMap({ + 'foo': 'foo', + }); + + sut['foo'] = 'bar'; + sut['bar'] = 'foo'; + + expect(sut['foo'], 'bar'); + expect(sut['bar'], 'foo'); + }); + + test('clear', () { + final sut = AccessAwareMap({ + 'foo': 'foo', + }); + + sut.clear(); + + expect(sut.accessedKeysWithValues.isEmpty, true); + expect(sut.isEmpty, true); + }); + + test('keys', () { + final sut = AccessAwareMap({ + 'foo': 'foo', + 'bar': 'bar', + }); + expect(sut.keys.toSet(), {'foo', 'bar'}); + }); + + test('remove', () { + final sut = AccessAwareMap({'foo': 'foo'}); + + sut.remove('foo'); + + expect(sut['foo'], isNull); + }); + }); +} diff --git a/packages/dart/test/protocol/breadcrumb_test.dart b/packages/dart/test/protocol/breadcrumb_test.dart new file mode 100644 index 0000000000..f1f85f2ec8 --- /dev/null +++ b/packages/dart/test/protocol/breadcrumb_test.dart @@ -0,0 +1,229 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final timestamp = DateTime.now(); + + final breadcrumb = Breadcrumb( + message: 'message', + timestamp: timestamp, + data: {'key': 'value'}, + level: SentryLevel.warning, + category: 'category', + type: 'type', + unknown: testUnknown, + ); + + final breadcrumbJson = { + 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), + 'message': 'message', + 'category': 'category', + 'data': {'key': 'value'}, + 'level': 'warning', + 'type': 'type', + }; + breadcrumbJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = breadcrumb.toJson(); + + expect( + DeepCollectionEquality().equals(breadcrumbJson, json), + true, + ); + }); + + test('fromJson', () { + final breadcrumb = Breadcrumb.fromJson(breadcrumbJson); + final json = breadcrumb.toJson(); + + expect( + DeepCollectionEquality().equals(breadcrumbJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = breadcrumb; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + test('copyWith takes new values', () { + final data = breadcrumb; + + final timestamp = DateTime.now(); + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + message: 'message1', + timestamp: timestamp, + data: {'key1': 'value1'}, + level: SentryLevel.fatal, + category: 'category1', + type: 'type1', + ); + + expect('message1', copy.message); + expect(timestamp, copy.timestamp); + expect({'key1': 'value1'}, copy.data); + expect(SentryLevel.fatal, copy.level); + expect('category1', copy.category); + expect('type1', copy.type); + }); + }); + + group('ctor', () { + test('Breadcrumb http', () { + final breadcrumb = Breadcrumb.http( + url: Uri.parse('https://example.org'), + method: 'GET', + level: SentryLevel.fatal, + reason: 'OK', + statusCode: 200, + requestDuration: Duration(milliseconds: 55), + timestamp: DateTime.now(), + requestBodySize: 2, + responseBodySize: 3, + httpQuery: 'foo=bar', + httpFragment: 'baz'); + final json = breadcrumb.toJson(); + + expect(json, { + 'timestamp': + formatDateAsIso8601WithMillisPrecision(breadcrumb.timestamp), + 'category': 'http', + 'data': { + 'url': 'https://example.org', + 'method': 'GET', + 'status_code': 200, + 'reason': 'OK', + 'duration': '0:00:00.055000', + 'request_body_size': 2, + 'response_body_size': 3, + 'http.query': 'foo=bar', + 'http.fragment': 'baz', + 'start_timestamp': breadcrumb.timestamp.millisecondsSinceEpoch - 55, + 'end_timestamp': breadcrumb.timestamp.millisecondsSinceEpoch + }, + 'level': 'fatal', + 'type': 'http', + }); + }); + + test('Breadcrumb http', () { + final breadcrumb = Breadcrumb.http( + url: Uri.parse('https://example.org'), + method: 'GET', + requestDuration: Duration(milliseconds: 10), + ); + final json = breadcrumb.toJson(); + + expect(json, { + 'timestamp': + formatDateAsIso8601WithMillisPrecision(breadcrumb.timestamp), + 'category': 'http', + 'data': { + 'url': 'https://example.org', + 'method': 'GET', + 'duration': '0:00:00.010000', + 'start_timestamp': breadcrumb.timestamp.millisecondsSinceEpoch - 10, + 'end_timestamp': breadcrumb.timestamp.millisecondsSinceEpoch + }, + 'level': 'info', + 'type': 'http', + }); + }); + + test('Minimal Breadcrumb http', () { + final breadcrumb = Breadcrumb.http( + url: Uri.parse('https://example.org'), + method: 'GET', + ); + final json = breadcrumb.toJson(); + + expect(json, { + 'timestamp': + formatDateAsIso8601WithMillisPrecision(breadcrumb.timestamp), + 'category': 'http', + 'data': { + 'url': 'https://example.org', + 'method': 'GET', + }, + 'level': 'info', + 'type': 'http', + }); + }); + + test('Breadcrumb console', () { + final breadcrumb = Breadcrumb.console( + message: 'Foo Bar', + ); + final json = breadcrumb.toJson(); + + expect(json, { + 'message': 'Foo Bar', + 'timestamp': + formatDateAsIso8601WithMillisPrecision(breadcrumb.timestamp), + 'category': 'console', + 'type': 'debug', + 'level': 'info', + }); + }); + + test('extensive Breadcrumb console', () { + final breadcrumb = Breadcrumb.console( + message: 'Foo Bar', + level: SentryLevel.error, + data: {'foo': 'bar'}, + ); + final json = breadcrumb.toJson(); + + expect(json, { + 'message': 'Foo Bar', + 'timestamp': + formatDateAsIso8601WithMillisPrecision(breadcrumb.timestamp), + 'category': 'console', + 'type': 'debug', + 'level': 'error', + 'data': {'foo': 'bar'}, + }); + }); + + test('extensive Breadcrumb user interaction', () { + final time = DateTime.now().toUtc(); + final breadcrumb = Breadcrumb.userInteraction( + message: 'Foo Bar', + level: SentryLevel.error, + timestamp: time, + data: {'foo': 'bar'}, + subCategory: 'click', + viewId: 'foo', + viewClass: 'bar', + ); + final json = breadcrumb.toJson(); + + expect(json, { + 'message': 'Foo Bar', + 'timestamp': formatDateAsIso8601WithMillisPrecision(time), + 'category': 'ui.click', + 'type': 'user', + 'level': 'error', + 'data': { + 'foo': 'bar', + 'view.id': 'foo', + 'view.class': 'bar', + }, + }); + }); + }); +} diff --git a/packages/dart/test/protocol/contexts_test.dart b/packages/dart/test/protocol/contexts_test.dart new file mode 100644 index 0000000000..39a9fe7700 --- /dev/null +++ b/packages/dart/test/protocol/contexts_test.dart @@ -0,0 +1,127 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final _traceId = SentryId.fromId('1988bb1b6f0d4c509e232f0cb9aaeaea'); + final _spanId = SpanId.fromId('976e0cd945864f60'); + final _parentSpanId = SpanId.fromId('c9c9fc3f9d4346df'); + final _associatedEventId = + SentryId.fromId('8a32c0f9be1d34a5efb2c4a10d80de9a'); + + final _trace = SentryTraceContext( + traceId: _traceId, + spanId: _spanId, + operation: 'op', + parentSpanId: _parentSpanId, + sampled: true, + description: 'desc', + status: SpanStatus.ok(), + ); + + final _feedback = SentryFeedback( + message: 'fixture-message', + contactEmail: 'fixture-contactEmail', + name: 'fixture-name', + replayId: 'fixture-replayId', + url: "https://fixture-url.com", + associatedEventId: _associatedEventId, + ); + + final _contexts = Contexts( + device: SentryDevice(batteryLevel: 90.0), + operatingSystem: SentryOperatingSystem(name: 'name'), + runtimes: [SentryRuntime(name: 'name')], + app: SentryApp(name: 'name'), + browser: SentryBrowser(name: 'name'), + gpu: SentryGpu(id: 1), + culture: SentryCulture(locale: 'foo-bar'), + trace: _trace, + feedback: _feedback, + flags: SentryFeatureFlags(values: [ + SentryFeatureFlag(flag: 'name', result: true), + ]), + ); + + final _contextsJson = { + 'device': {'battery_level': 90.0}, + 'os': {'name': 'name'}, + 'runtime': {'name': 'name'}, + 'app': {'app_name': 'name'}, + 'browser': {'name': 'name'}, + 'gpu': {'id': 1}, + 'culture': {'locale': 'foo-bar'}, + 'trace': { + 'span_id': '976e0cd945864f60', + 'trace_id': '1988bb1b6f0d4c509e232f0cb9aaeaea', + 'op': 'op', + 'parent_span_id': 'c9c9fc3f9d4346df', + 'description': 'desc', + 'status': 'ok' + }, + 'feedback': { + 'message': 'fixture-message', + 'contact_email': 'fixture-contactEmail', + 'name': 'fixture-name', + 'replay_id': 'fixture-replayId', + 'url': 'https://fixture-url.com', + 'associated_event_id': '8a32c0f9be1d34a5efb2c4a10d80de9a', + }, + 'flags': { + 'values': [ + {'flag': 'name', 'result': true} + ], + }, + }; + + final _contextsMutlipleRuntimes = Contexts( + runtimes: [ + SentryRuntime(name: 'name'), + SentryRuntime(name: 'name'), + SentryRuntime(key: 'key') + ], + ); + + final _contextsMutlipleRuntimesJson = { + 'name': {'name': 'name', 'type': 'runtime'}, + 'name0': {'name': 'name', 'type': 'runtime'}, + }; + + group('json', () { + test('toJson', () { + final json = _contexts.toJson(); + + expect( + DeepCollectionEquality().equals(_contextsJson, json), + true, + ); + }); + test('toJson multiple runtimes', () { + final json = _contextsMutlipleRuntimes.toJson(); + + expect( + DeepCollectionEquality().equals(_contextsMutlipleRuntimesJson, json), + true, + ); + }); + test('fromJson', () { + final contexts = Contexts.fromJson(_contextsJson); + final json = contexts.toJson(); + + expect( + DeepCollectionEquality().equals(_contextsJson, json), + true, + ); + }); + test('fromJson multiple runtimes', () { + final contextsMutlipleRuntimes = + Contexts.fromJson(_contextsMutlipleRuntimesJson); + final json = contextsMutlipleRuntimes.toJson(); + + expect( + DeepCollectionEquality().equals(_contextsMutlipleRuntimesJson, json), + true, + ); + }); + }); +} diff --git a/packages/dart/test/protocol/culture_test.dart b/packages/dart/test/protocol/culture_test.dart new file mode 100644 index 0000000000..681af25d9d --- /dev/null +++ b/packages/dart/test/protocol/culture_test.dart @@ -0,0 +1,55 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; +import 'package:collection/collection.dart'; + +void main() { + group(SentryCulture, () { + test('copyWith keeps unchanged', () { + final data = _generate(); + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = _generate(); + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + calendar: 'calendar', + displayName: 'displayName', + is24HourFormat: false, // opposite of the value from _generate + locale: 'locale', + timezone: 'timezone', + ); + + expect('calendar', copy.calendar); + expect('displayName', copy.displayName); + expect(false, copy.is24HourFormat); + expect('locale', copy.locale); + expect('timezone', copy.timezone); + }); + test('toJson', () { + final data = _generate(); + + expect(data.toJson(), { + 'calendar': 'FooCalendar', + 'display_name': 'FooLanguage', + 'is_24_hour_format': true, + 'locale': 'fo-ba', + 'timezone': 'best-timezone', + }); + }); + }); +} + +SentryCulture _generate() => SentryCulture( + calendar: 'FooCalendar', + displayName: 'FooLanguage', + is24HourFormat: true, + locale: 'fo-ba', + timezone: 'best-timezone', + ); diff --git a/packages/dart/test/protocol/debug_image_test.dart b/packages/dart/test/protocol/debug_image_test.dart new file mode 100644 index 0000000000..87dddaaffe --- /dev/null +++ b/packages/dart/test/protocol/debug_image_test.dart @@ -0,0 +1,94 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final debugImage = DebugImage( + type: 'type', + imageAddr: 'imageAddr', + debugId: 'debugId', + debugFile: 'debugFile', + imageSize: 1, + uuid: 'uuid', + codeFile: 'codeFile', + arch: 'arch', + codeId: 'codeId', + unknown: testUnknown, + ); + + final debugImageJson = { + 'uuid': 'uuid', + 'type': 'type', + 'debug_id': 'debugId', + 'debug_file': 'debugFile', + 'code_file': 'codeFile', + 'image_addr': 'imageAddr', + 'image_size': 1, + 'arch': 'arch', + 'code_id': 'codeId', + }; + debugImageJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = debugImage.toJson(); + + expect( + MapEquality().equals(debugImageJson, json), + true, + ); + }); + test('fromJson', () { + final debugImage = DebugImage.fromJson(debugImageJson); + final json = debugImage.toJson(); + + expect( + MapEquality().equals(debugImageJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = debugImage; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = debugImage; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + type: 'type1', + name: 'name', + imageAddr: 'imageAddr1', + imageVmAddr: 'imageVmAddr1', + debugId: 'debugId1', + debugFile: 'debugFile1', + imageSize: 2, + uuid: 'uuid1', + codeFile: 'codeFile1', + arch: 'arch1', + codeId: 'codeId1', + ); + + expect('type1', copy.type); + expect('imageAddr1', copy.imageAddr); + expect('debugId1', copy.debugId); + expect('debugFile1', copy.debugFile); + expect(2, copy.imageSize); + expect('uuid1', copy.uuid); + expect('codeFile1', copy.codeFile); + expect('arch1', copy.arch); + expect('codeId1', copy.codeId); + }); + }); +} diff --git a/packages/dart/test/protocol/debug_meta_test.dart b/packages/dart/test/protocol/debug_meta_test.dart new file mode 100644 index 0000000000..0a70307369 --- /dev/null +++ b/packages/dart/test/protocol/debug_meta_test.dart @@ -0,0 +1,43 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final debugMeta = DebugMeta( + sdk: SdkInfo( + sdkName: 'sdkName', + ), + images: [DebugImage(type: 'macho', uuid: 'uuid')], + unknown: testUnknown, + ); + + final debugMetaJson = { + 'sdk_info': {'sdk_name': 'sdkName'}, + 'images': [ + {'uuid': 'uuid', 'type': 'macho'} + ] + }; + debugMetaJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = debugMeta.toJson(); + + expect( + DeepCollectionEquality().equals(debugMetaJson, json), + true, + ); + }); + test('fromJson', () { + final debugMeta = DebugMeta.fromJson(debugMetaJson); + final json = debugMeta.toJson(); + + expect( + DeepCollectionEquality().equals(debugMetaJson, json), + true, + ); + }); + }); +} diff --git a/packages/dart/test/protocol/mechanism_test.dart b/packages/dart/test/protocol/mechanism_test.dart new file mode 100644 index 0000000000..e8675d9658 --- /dev/null +++ b/packages/dart/test/protocol/mechanism_test.dart @@ -0,0 +1,97 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final mechanism = Mechanism( + type: 'type', + description: 'description', + helpLink: 'helpLink', + handled: true, + synthetic: true, + meta: {'key': 'value'}, + data: {'keyb': 'valueb'}, + isExceptionGroup: false, + exceptionId: 0, + parentId: 0, + source: 'source', + unknown: testUnknown, + ); + + final mechanismJson = { + 'type': 'type', + 'description': 'description', + 'help_link': 'helpLink', + 'handled': true, + 'meta': {'key': 'value'}, + 'data': {'keyb': 'valueb'}, + 'synthetic': true, + 'is_exception_group': false, + 'source': 'source', + 'exception_id': 0, + 'parent_id': 0, + }; + mechanismJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = mechanism.toJson(); + + expect( + DeepCollectionEquality().equals(mechanismJson, json), + true, + ); + }); + test('fromJson', () { + final mechanism = Mechanism.fromJson(mechanismJson); + final json = mechanism.toJson(); + + expect( + DeepCollectionEquality().equals(mechanismJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = mechanism; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect(data.toJson(), copy.toJson()); + }); + + test('copyWith takes new values', () { + final data = mechanism; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + type: 'type1', + description: 'description1', + helpLink: 'helpLink1', + handled: false, + synthetic: false, + meta: {'key1': 'value1'}, + data: {'keyb1': 'valueb1'}, + exceptionId: 1, + parentId: 1, + isExceptionGroup: false, + source: 'foo', + ); + + expect('type1', copy.type); + expect('description1', copy.description); + expect('helpLink1', copy.helpLink); + expect(false, copy.handled); + expect(false, copy.synthetic); + expect({'key1': 'value1'}, copy.meta); + expect({'keyb1': 'valueb1'}, copy.data); + expect(1, copy.exceptionId); + expect(1, copy.parentId); + expect(false, copy.isExceptionGroup); + expect('foo', copy.source); + }); + }); +} diff --git a/packages/dart/test/protocol/rate_limit_parser_test.dart b/packages/dart/test/protocol/rate_limit_parser_test.dart new file mode 100644 index 0000000000..567dec34f0 --- /dev/null +++ b/packages/dart/test/protocol/rate_limit_parser_test.dart @@ -0,0 +1,146 @@ +import 'package:sentry/src/transport/rate_limit_parser.dart'; +import 'package:sentry/src/transport/data_category.dart'; +import 'package:test/test.dart'; + +void main() { + group('parseRateLimitHeader', () { + test('single rate limit with single category', () { + final sut = RateLimitParser('50:transaction').parseRateLimitHeader(); + + expect(sut.length, 1); + expect(sut[0].category, DataCategory.transaction); + expect(sut[0].duration.inMilliseconds, 50000); + }); + + test('single rate limit with multiple categories', () { + final sut = + RateLimitParser('50:transaction;session').parseRateLimitHeader(); + + expect(sut.length, 2); + expect(sut[0].category, DataCategory.transaction); + expect(sut[0].duration.inMilliseconds, 50000); + expect(sut[1].category, DataCategory.session); + expect(sut[1].duration.inMilliseconds, 50000); + }); + + test('don`t apply rate limit for unknown categories ', () { + final sut = RateLimitParser('50:somethingunknown').parseRateLimitHeader(); + + expect(sut.length, 0); + }); + + test('apply all if there are no categories', () { + final sut = RateLimitParser('50::key').parseRateLimitHeader(); + + expect(sut.length, 1); + expect(sut[0].category, DataCategory.all); + expect(sut[0].duration.inMilliseconds, 50000); + }); + + test('multiple rate limits', () { + final sut = + RateLimitParser('50:transaction, 70:session').parseRateLimitHeader(); + + expect(sut.length, 2); + expect(sut[0].category, DataCategory.transaction); + expect(sut[0].duration.inMilliseconds, 50000); + expect(sut[1].category, DataCategory.session); + expect(sut[1].duration.inMilliseconds, 70000); + }); + + test('multiple rate limits with same category', () { + final sut = RateLimitParser('50:transaction, 70:transaction') + .parseRateLimitHeader(); + + expect(sut.length, 2); + expect(sut[0].category, DataCategory.transaction); + expect(sut[0].duration.inMilliseconds, 50000); + expect(sut[1].category, DataCategory.transaction); + expect(sut[1].duration.inMilliseconds, 70000); + }); + + test('ignore case', () { + final sut = RateLimitParser('50:TRANSACTION').parseRateLimitHeader(); + + expect(sut.length, 1); + expect(sut[0].category, DataCategory.transaction); + expect(sut[0].duration.inMilliseconds, 50000); + }); + + test('un-parseable returns default duration', () { + final sut = RateLimitParser('foobar:transaction').parseRateLimitHeader(); + + expect(sut.length, 1); + expect(sut[0].category, DataCategory.transaction); + expect(sut[0].duration.inMilliseconds, + RateLimitParser.httpRetryAfterDefaultDelay.inMilliseconds); + }); + + test('do not parse namespaces if not metric_bucket', () { + final sut = + RateLimitParser('1:transaction:organization:quota_exceeded:custom') + .parseRateLimitHeader(); + + expect(sut.length, 1); + expect(sut[0].category, DataCategory.transaction); + expect(sut[0].namespaces, isEmpty); + }); + + test('parse namespaces on metric_bucket', () { + final sut = + RateLimitParser('1:metric_bucket:organization:quota_exceeded:custom') + .parseRateLimitHeader(); + + expect(sut.length, 1); + expect(sut[0].category, DataCategory.metricBucket); + expect(sut[0].namespaces, isNotEmpty); + expect(sut[0].namespaces.first, 'custom'); + }); + + test('parse empty namespaces on metric_bucket', () { + final sut = + RateLimitParser('1:metric_bucket:organization:quota_exceeded:') + .parseRateLimitHeader(); + + expect(sut.length, 1); + expect(sut[0].category, DataCategory.metricBucket); + expect(sut[0].namespaces, isEmpty); + }); + + test('parse missing namespaces on metric_bucket', () { + final sut = RateLimitParser('1:metric_bucket').parseRateLimitHeader(); + + expect(sut.length, 1); + expect(sut[0].category, DataCategory.metricBucket); + expect(sut[0].namespaces, isEmpty); + }); + }); + + group('parseRetryAfterHeader', () { + test('null returns default category all with default duration', () { + final sut = RateLimitParser(null).parseRetryAfterHeader(); + + expect(sut.length, 1); + expect(sut[0].category, DataCategory.all); + expect(sut[0].duration.inMilliseconds, + RateLimitParser.httpRetryAfterDefaultDelay.inMilliseconds); + }); + + test('parseable returns default category with duration in millis', () { + final sut = RateLimitParser('8').parseRetryAfterHeader(); + + expect(sut.length, 1); + expect(sut[0].category, DataCategory.all); + expect(sut[0].duration.inMilliseconds, 8000); + }); + + test('un-parseable returns default category with default duration', () { + final sut = RateLimitParser('foobar').parseRetryAfterHeader(); + + expect(sut.length, 1); + expect(sut[0].category, DataCategory.all); + expect(sut[0].duration.inMilliseconds, + RateLimitParser.httpRetryAfterDefaultDelay.inMilliseconds); + }); + }); +} diff --git a/packages/dart/test/protocol/rate_limiter_test.dart b/packages/dart/test/protocol/rate_limiter_test.dart new file mode 100644 index 0000000000..9a6079810b --- /dev/null +++ b/packages/dart/test/protocol/rate_limiter_test.dart @@ -0,0 +1,350 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/client_reports/discard_reason.dart'; +import 'package:sentry/src/sentry_envelope_header.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:sentry/src/transport/data_category.dart'; +import 'package:sentry/src/transport/rate_limiter.dart'; +import 'package:test/test.dart'; + +import '../mocks/mock_client_report_recorder.dart'; +import '../mocks/mock_hub.dart'; +import '../test_utils.dart'; + +void main() { + var fixture = Fixture(); + + setUp(() { + fixture = Fixture(); + }); + + test('uses X-Sentry-Rate-Limit and allows sending if time has passed', () { + final rateLimiter = fixture.getSut(); + fixture.dateTimeToReturn = 0; + + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits( + '50:transaction:key, 1:default;error;security:organization', null, 1); + + fixture.dateTimeToReturn = 1001; + + final result = rateLimiter.filter(envelope); + expect(result, isNotNull); + expect(result!.items.length, 1); + }); + + test( + 'parse X-Sentry-Rate-Limit and set its values and retry after should be true', + () { + final rateLimiter = fixture.getSut(); + fixture.dateTimeToReturn = 0; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits( + '50:transaction:key, 2700:default;error;security:organization', + null, + 1); + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test( + 'parse X-Sentry-Rate-Limit and set its values and retry after should be false', + () { + final rateLimiter = fixture.getSut(); + fixture.dateTimeToReturn = 0; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits( + '1:transaction:key, 1:default;error;security:organization', null, 1); + + fixture.dateTimeToReturn = 1001; + + final result = rateLimiter.filter(envelope); + expect(result, isNotNull); + expect(1, result!.items.length); + }); + + test( + 'When X-Sentry-Rate-Limit categories are empty, applies to all the categories', + () { + final rateLimiter = fixture.getSut(); + fixture.dateTimeToReturn = 0; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits('50::key', null, 1); + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test( + 'When all categories is set but expired, applies only for specific category', + () { + final rateLimiter = fixture.getSut(); + fixture.dateTimeToReturn = 0; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits( + '1::key, 60:default;error;security:organization', null, 1); + + fixture.dateTimeToReturn = 1001; + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test('When category has shorter rate limiting, do not apply new timestamp', + () { + final rateLimiter = fixture.getSut(); + fixture.dateTimeToReturn = 0; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits( + '60:error:key, 1:error:organization', null, 1); + + fixture.dateTimeToReturn = 1001; + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test('When category has longer rate limiting, apply new timestamp', () { + final rateLimiter = fixture.getSut(); + fixture.dateTimeToReturn = 0; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits( + '1:error:key, 5:error:organization', null, 1); + + fixture.dateTimeToReturn = 1001; + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test('When both retry headers are not present, default delay is set', () { + final rateLimiter = fixture.getSut(); + fixture.dateTimeToReturn = 0; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits(null, null, 429); + + fixture.dateTimeToReturn = 1001; + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test( + 'When no sentryRateLimitHeader available, it fallback to retryAfterHeader', + () { + final rateLimiter = fixture.getSut(); + fixture.dateTimeToReturn = 0; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits(null, '50', 429); + + fixture.dateTimeToReturn = 1001; + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test('dropping of event recorded', () { + final rateLimiter = fixture.getSut(); + + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final eventEnvelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits( + '1:error:key, 5:error:organization', null, 1); + + final result = rateLimiter.filter(eventEnvelope); + expect(result, isNull); + + expect(fixture.mockRecorder.discardedEvents.first.category, + DataCategory.error); + expect(fixture.mockRecorder.discardedEvents.first.reason, + DiscardReason.rateLimitBackoff); + }); + + test('dropping of transaction recorded', () { + final rateLimiter = fixture.getSut(); + + final transaction = fixture.getTransaction(); + transaction.tracer.startChild('child1'); + transaction.tracer.startChild('child2'); + final eventItem = SentryEnvelopeItem.fromTransaction(transaction); + final eventEnvelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits( + '1:transaction:key, 5:transaction:organization', null, 1); + + final result = rateLimiter.filter(eventEnvelope); + expect(result, isNull); + + expect(fixture.mockRecorder.discardedEvents.length, 2); + + final transactionDiscardedEvent = fixture.mockRecorder.discardedEvents + .firstWhereOrNull((element) => + element.category == DataCategory.transaction && + element.reason == DiscardReason.rateLimitBackoff); + + final spanDiscardedEvent = fixture.mockRecorder.discardedEvents + .firstWhereOrNull((element) => + element.category == DataCategory.span && + element.reason == DiscardReason.rateLimitBackoff); + + expect(transactionDiscardedEvent, isNotNull); + expect(spanDiscardedEvent, isNotNull); + expect(spanDiscardedEvent!.quantity, 3); + }); + + group('apply rateLimit', () { + test('error', () { + final rateLimiter = fixture.getSut(); + fixture.dateTimeToReturn = 0; + + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits( + '1:error:key, 5:error:organization', null, 1); + + expect(rateLimiter.filter(envelope), isNull); + }); + + test('transaction', () { + final rateLimiter = fixture.getSut(); + fixture.dateTimeToReturn = 0; + + final transaction = fixture.getTransaction(); + final eventItem = SentryEnvelopeItem.fromTransaction(transaction); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + rateLimiter.updateRetryAfterLimits( + '1:transaction:key, 5:transaction:organization', null, 1); + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test('log', () { + final rateLimiter = fixture.getSut(); + fixture.dateTimeToReturn = 0; + + final log = SentryLog( + timestamp: DateTime.now(), + traceId: SentryId.newId(), + level: SentryLogLevel.info, + body: 'test', + attributes: { + 'test': SentryLogAttribute.string('test'), + }, + ); + + final sdkVersion = SdkVersion(name: 'test', version: 'test'); + final envelope = SentryEnvelope.fromLogs([log], sdkVersion); + + rateLimiter.updateRetryAfterLimits( + '1:log_item:key, 5:log_item:organization', null, 1); + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + }); + + group('$DataCategory', () { + test('fromItemType', () { + expect(DataCategory.fromItemType('event'), DataCategory.error); + expect(DataCategory.fromItemType('session'), DataCategory.session); + expect(DataCategory.fromItemType('attachment'), DataCategory.attachment); + expect( + DataCategory.fromItemType('transaction'), DataCategory.transaction); + expect(DataCategory.fromItemType('statsd'), DataCategory.metricBucket); + expect(DataCategory.fromItemType('log'), DataCategory.logItem); + expect(DataCategory.fromItemType('unknown'), DataCategory.unknown); + }); + }); +} + +class Fixture { + var dateTimeToReturn = 0; + + late var mockRecorder = MockClientReportRecorder(); + + RateLimiter getSut() { + final options = defaultTestOptions(); + options.clock = _currentDateTime; + options.recorder = mockRecorder; + + return RateLimiter(options); + } + + DateTime _currentDateTime() { + return DateTime.fromMillisecondsSinceEpoch(dateTimeToReturn); + } + + SentryTransaction getTransaction() { + final context = SentryTransactionContext( + 'name', + 'op', + samplingDecision: SentryTracesSamplingDecision(true), + ); + final tracer = SentryTracer(context, MockHub()); + return SentryTransaction(tracer); + } +} diff --git a/packages/dart/test/protocol/sdk_info_test.dart b/packages/dart/test/protocol/sdk_info_test.dart new file mode 100644 index 0000000000..612073eb82 --- /dev/null +++ b/packages/dart/test/protocol/sdk_info_test.dart @@ -0,0 +1,74 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final sdkInfo = SdkInfo( + sdkName: 'sdkName', + versionMajor: 1, + versionMinor: 2, + versionPatchlevel: 3, + unknown: testUnknown, + ); + + final sdkInfoJson = { + 'sdk_name': 'sdkName', + 'version_major': 1, + 'version_minor': 2, + 'version_patchlevel': 3, + }; + sdkInfoJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = sdkInfo.toJson(); + + expect( + MapEquality().equals(sdkInfoJson, json), + true, + ); + }); + test('fromJson', () { + final sdkInfo = SdkInfo.fromJson(sdkInfoJson); + final json = sdkInfo.toJson(); + + print(sdkInfo); + print(json); + + expect( + MapEquality().equals(sdkInfoJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sdkInfo; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + test('copyWith takes new values', () { + final data = sdkInfo; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + sdkName: 'sdkName1', + versionMajor: 11, + versionMinor: 22, + versionPatchlevel: 33, + ); + + expect('sdkName1', copy.sdkName); + expect(11, copy.versionMajor); + expect(22, copy.versionMinor); + expect(33, copy.versionPatchlevel); + }); + }); +} diff --git a/packages/dart/test/protocol/sdk_version_test.dart b/packages/dart/test/protocol/sdk_version_test.dart new file mode 100644 index 0000000000..26ddddbb40 --- /dev/null +++ b/packages/dart/test/protocol/sdk_version_test.dart @@ -0,0 +1,111 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + group('json', () { + final fixture = Fixture(); + + test('toJson', () { + final json = fixture.getSut().toJson(); + + expect( + DeepCollectionEquality().equals(fixture.sdkVersionJson, json), + true, + ); + }); + test('fromJson', () { + final sdkVersion = SdkVersion.fromJson(fixture.sdkVersionJson); + final json = sdkVersion.toJson(); + + expect( + DeepCollectionEquality().equals(fixture.sdkVersionJson, json), + true, + ); + }); + }); + + group('copyWith', () { + final fixture = Fixture(); + + test('copyWith keeps unchanged', () { + final sut = fixture.getSut(); + // ignore: deprecated_member_use_from_same_package + final copy = sut.copyWith(); + + expect(sut.toJson(), copy.toJson()); + }); + + test('copyWith takes new values', () { + final sut = fixture.getSut(); + + final packages = [SentryPackage('name1', 'version1')]; + final integrations = ['test1']; + // ignore: deprecated_member_use_from_same_package + final copy = sut.copyWith( + name: 'name1', + version: 'version1', + integrations: integrations, + packages: packages, + ); + + expect( + ListEquality().equals(integrations, copy.integrations), + true, + ); + expect( + ListEquality().equals(packages, copy.packages), + true, + ); + expect('name1', copy.name); + expect('version1', copy.version); + }); + }); + + group('addPackage', () { + final fixture = Fixture(); + + test('add package if not same name and version', () { + final sut = fixture.getSut(); + sut.addPackage('name1', 'version1'); + + final last = sut.packages.last; + expect('name1', last.name); + expect('version1', last.version); + }); + test('does not add package if the same name and version', () { + final sut = fixture.getSut(); + sut.addPackage('name', 'version'); + + expect(1, sut.packages.length); + }); + }); +} + +class Fixture { + final sdkVersionJson = { + 'name': 'name', + 'version': 'version', + 'integrations': ['test'], + 'packages': [ + { + 'name': 'name', + 'version': 'version', + } + ], + }; + + Fixture() { + sdkVersionJson.addAll(testUnknown); + } + + SdkVersion getSut() => SdkVersion( + name: 'name', + version: 'version', + integrations: ['test'], + packages: [SentryPackage('name', 'version')], + unknown: testUnknown, + ); +} diff --git a/packages/dart/test/protocol/sentry_app_test.dart b/packages/dart/test/protocol/sentry_app_test.dart new file mode 100644 index 0000000000..0bbf9c0fcf --- /dev/null +++ b/packages/dart/test/protocol/sentry_app_test.dart @@ -0,0 +1,112 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final testStartTime = DateTime.fromMicrosecondsSinceEpoch(0); + + final sentryApp = SentryApp( + name: 'fixture-name', + version: 'fixture-version', + identifier: 'fixture-identifier', + build: 'fixture-build', + buildType: 'fixture-buildType', + startTime: testStartTime, + deviceAppHash: 'fixture-deviceAppHash', + inForeground: true, + viewNames: ['fixture-viewName', 'fixture-viewName2'], + textScale: 2.0, + unknown: testUnknown, + ); + + final sentryAppJson = { + 'app_name': 'fixture-name', + 'app_version': 'fixture-version', + 'app_identifier': 'fixture-identifier', + 'app_build': 'fixture-build', + 'build_type': 'fixture-buildType', + 'app_start_time': testStartTime.toIso8601String(), + 'device_app_hash': 'fixture-deviceAppHash', + 'in_foreground': true, + 'view_names': ['fixture-viewName', 'fixture-viewName2'], + 'text_scale': 2.0, + }; + sentryAppJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = sentryApp.toJson(); + + expect(json['app_name'], 'fixture-name'); + expect(json['app_version'], 'fixture-version'); + expect(json['app_identifier'], 'fixture-identifier'); + expect(json['app_build'], 'fixture-build'); + expect(json['build_type'], 'fixture-buildType'); + expect(json['app_start_time'], testStartTime.toIso8601String()); + expect(json['device_app_hash'], 'fixture-deviceAppHash'); + expect(json['in_foreground'], true); + expect(json['view_names'], ['fixture-viewName', 'fixture-viewName2']); + expect(json['text_scale'], 2.0); + }); + test('fromJson', () { + final sentryApp = SentryApp.fromJson(sentryAppJson); + final json = sentryApp.toJson(); + + expect(json['app_name'], 'fixture-name'); + expect(json['app_version'], 'fixture-version'); + expect(json['app_identifier'], 'fixture-identifier'); + expect(json['app_build'], 'fixture-build'); + expect(json['build_type'], 'fixture-buildType'); + expect(json['app_start_time'], testStartTime.toIso8601String()); + expect(json['device_app_hash'], 'fixture-deviceAppHash'); + expect(json['in_foreground'], true); + expect(json['view_names'], ['fixture-viewName', 'fixture-viewName2']); + expect(json['text_scale'], 2.0); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryApp; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryApp; + + final startTime = DateTime.now(); + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + name: 'name1', + version: 'version1', + identifier: 'identifier1', + build: 'build1', + buildType: 'buildType1', + startTime: startTime, + deviceAppHash: 'hash1', + inForeground: true, + viewNames: ['screen1'], + textScale: 3.0, + ); + + expect('name1', copy.name); + expect('version1', copy.version); + expect('identifier1', copy.identifier); + expect('build1', copy.build); + expect('buildType1', copy.buildType); + expect(startTime, copy.startTime); + expect('hash1', copy.deviceAppHash); + expect(true, copy.inForeground); + expect(['screen1'], copy.viewNames); + expect(3.0, copy.textScale); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_baggage_header_test.dart b/packages/dart/test/protocol/sentry_baggage_header_test.dart new file mode 100644 index 0000000000..ef792877f9 --- /dev/null +++ b/packages/dart/test/protocol/sentry_baggage_header_test.dart @@ -0,0 +1,43 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + group('$SentryBaggageHeader', () { + test('name is baggage', () { + final baggage = SentryBaggageHeader(''); + + expect(baggage.name, 'baggage'); + }); + + test('baggage header from baggage', () { + final baggage = SentryBaggage({}); + final id = SentryId.newId().toString(); + baggage.setTraceId(id); + baggage.setPublicKey('publicKey'); + baggage.setRelease('release'); + baggage.setEnvironment('environment'); + baggage.setUserId('userId'); + baggage.setTransaction('transaction'); + baggage.setSampleRate('1.0'); + baggage.setSampleRand('0.4'); + baggage.setSampled('false'); + final replayId = SentryId.newId().toString(); + baggage.setReplayId(replayId); + + final baggageHeader = SentryBaggageHeader.fromBaggage(baggage); + + expect( + baggageHeader.value, + 'sentry-trace_id=$id,' + 'sentry-public_key=publicKey,' + 'sentry-release=release,' + 'sentry-environment=environment,' + 'sentry-user_id=userId,' + 'sentry-transaction=transaction,' + 'sentry-sample_rate=1.0,' + 'sentry-sample_rand=0.4,' + 'sentry-sampled=false,' + 'sentry-replay_id=$replayId'); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_browser_test.dart b/packages/dart/test/protocol/sentry_browser_test.dart new file mode 100644 index 0000000000..13096237a9 --- /dev/null +++ b/packages/dart/test/protocol/sentry_browser_test.dart @@ -0,0 +1,60 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final sentryBrowser = SentryBrowser( + name: 'fixture-name', + version: 'fixture-version', + ); + + final sentryBrowserJson = { + 'name': 'fixture-name', + 'version': 'fixture-version', + }; + + group('json', () { + test('toJson', () { + final json = sentryBrowser.toJson(); + + expect( + MapEquality().equals(sentryBrowserJson, json), + true, + ); + }); + test('fromJson', () { + final sentryBrowser = SentryBrowser.fromJson(sentryBrowserJson); + final json = sentryBrowser.toJson(); + + expect( + MapEquality().equals(sentryBrowserJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryBrowser; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryBrowser; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + name: 'name1', + version: 'version1', + ); + + expect('name1', copy.name); + expect('version1', copy.version); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_device_test.dart b/packages/dart/test/protocol/sentry_device_test.dart new file mode 100644 index 0000000000..1921f23fee --- /dev/null +++ b/packages/dart/test/protocol/sentry_device_test.dart @@ -0,0 +1,350 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final testBootTime = DateTime.fromMicrosecondsSinceEpoch(0); + + final sentryDevice = SentryDevice( + name: 'testDevice', + family: 'testFamily', + model: 'testModel', + modelId: 'testModelId', + arch: 'testArch', + batteryLevel: 23.0, + orientation: SentryOrientation.landscape, + manufacturer: 'testOEM', + brand: 'testBrand', + screenDensity: 99.1, + screenDpi: 100, + online: false, + charging: true, + lowMemory: false, + simulator: true, + memorySize: 1234567, + freeMemory: 12345, + usableMemory: 9876, + storageSize: 1234567, + freeStorage: 1234567, + externalStorageSize: 98765, + externalFreeStorage: 98765, + bootTime: testBootTime, + batteryStatus: 'Unknown', + cpuDescription: 'M1 Pro Max Ultra', + deviceType: 'Flutter Device', + deviceUniqueIdentifier: 'uuid', + processorCount: 4, + processorFrequency: 1.2, + supportsAccelerometer: true, + supportsGyroscope: true, + supportsAudio: true, + supportsLocationService: true, + supportsVibration: true, + screenHeightPixels: 100, + screenWidthPixels: 100, + unknown: testUnknown, + ); + + final sentryDeviceJson = { + 'name': 'testDevice', + 'family': 'testFamily', + 'model': 'testModel', + 'model_id': 'testModelId', + 'arch': 'testArch', + 'battery_level': 23.0, + 'orientation': 'landscape', + 'manufacturer': 'testOEM', + 'brand': 'testBrand', + 'screen_density': 99.1, + 'screen_dpi': 100, + 'online': false, + 'charging': true, + 'low_memory': false, + 'simulator': true, + 'memory_size': 1234567, + 'free_memory': 12345, + 'usable_memory': 9876, + 'storage_size': 1234567, + 'free_storage': 1234567, + 'external_storage_size': 98765, + 'external_free_storage': 98765, + 'boot_time': testBootTime.toIso8601String(), + 'battery_status': 'Unknown', + 'cpu_description': 'M1 Pro Max Ultra', + 'device_type': 'Flutter Device', + 'device_unique_identifier': 'uuid', + 'processor_count': 4, + 'processor_frequency': 1.2, + 'supports_accelerometer': true, + 'supports_gyroscope': true, + 'supports_audio': true, + 'supports_location_service': true, + 'supports_vibration': true, + 'screen_height_pixels': 100, + 'screen_width_pixels': 100, + }; + sentryDeviceJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = sentryDevice.toJson(); + + expect( + MapEquality().equals(sentryDeviceJson, json), + true, + ); + }); + + test('fromJson', () { + final sentryDevice = SentryDevice.fromJson(sentryDeviceJson); + final json = sentryDevice.toJson(); + + expect( + MapEquality().equals(sentryDeviceJson, json), + true, + ); + }); + + test('fromJson double screen_height_pixels and screen_width_pixels', () { + sentryDeviceJson['screen_height_pixels'] = 100.0; + sentryDeviceJson['screen_width_pixels'] = 100.0; + + final sentryDevice = SentryDevice.fromJson(sentryDeviceJson); + final json = sentryDevice.toJson(); + + expect( + MapEquality().equals(sentryDeviceJson, json), + true, + ); + }); + + test('batery level converts int to double', () { + final map = {'battery_level': 1}; + + final sentryDevice = SentryDevice.fromJson(map); + + expect( + sentryDevice.batteryLevel, + 1.0, + ); + }); + + test('batery level maps double', () { + final map = {'battery_level': 1.0}; + + final sentryDevice = SentryDevice.fromJson(map); + + expect( + sentryDevice.batteryLevel, + 1.0, + ); + }); + + test('batery level ignores if not a num', () { + final map = {'battery_level': 'abc'}; + + final sentryDevice = SentryDevice.fromJson(map); + + expect( + sentryDevice.batteryLevel, + null, + ); + }); + }); + + test('copyWith keeps unchanged', () { + final data = _generate(); + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = _generate(); + + final bootTime = DateTime.now(); + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + name: 'name1', + family: 'family1', + model: 'model1', + modelId: 'modelId1', + arch: 'arch1', + batteryLevel: 2, + orientation: SentryOrientation.portrait, + manufacturer: 'manufacturer1', + brand: 'brand1', + screenHeightPixels: 900, + screenWidthPixels: 700, + screenDensity: 99.2, + screenDpi: 99, + online: true, + charging: false, + lowMemory: true, + simulator: false, + memorySize: 12345678, + freeMemory: 123456, + usableMemory: 98765, + storageSize: 12345678, + freeStorage: 12345678, + externalStorageSize: 987654, + externalFreeStorage: 987654, + bootTime: bootTime, + ); + + expect('name1', copy.name); + expect('family1', copy.family); + expect('model1', copy.model); + expect('modelId1', copy.modelId); + expect('arch1', copy.arch); + expect(2, copy.batteryLevel); + expect(SentryOrientation.portrait, copy.orientation); + expect('manufacturer1', copy.manufacturer); + expect('brand1', copy.brand); + expect(900, copy.screenHeightPixels); + expect(700, copy.screenWidthPixels); + expect(99.2, copy.screenDensity); + expect(99, copy.screenDpi); + expect(true, copy.online); + expect(false, copy.charging); + expect(true, copy.lowMemory); + expect(false, copy.simulator); + expect(12345678, copy.memorySize); + expect(123456, copy.freeMemory); + expect(98765, copy.usableMemory); + expect(12345678, copy.storageSize); + expect(12345678, copy.freeStorage); + expect(987654, copy.externalStorageSize); + expect(987654, copy.externalFreeStorage); + expect(bootTime, copy.bootTime); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryDevice; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryDevice; + + final bootTime = DateTime.now(); + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + name: 'name1', + family: 'family1', + model: 'model1', + modelId: 'modelId1', + arch: 'arch1', + batteryLevel: 2, + orientation: SentryOrientation.portrait, + manufacturer: 'manufacturer1', + brand: 'brand1', + screenDensity: 99.2, + screenDpi: 99, + online: true, + charging: false, + lowMemory: true, + simulator: false, + memorySize: 12345678, + freeMemory: 123456, + usableMemory: 98765, + storageSize: 12345678, + freeStorage: 12345678, + externalStorageSize: 987654, + externalFreeStorage: 987654, + bootTime: bootTime, + batteryStatus: 'Charging', + cpuDescription: 'Intel i9', + deviceType: 'Tablet', + deviceUniqueIdentifier: 'foo_bar_baz', + processorCount: 8, + processorFrequency: 3.4, + supportsAccelerometer: false, + supportsGyroscope: false, + supportsAudio: false, + supportsLocationService: false, + supportsVibration: false, + screenHeightPixels: 2, + screenWidthPixels: 2, + ); + + expect('name1', copy.name); + expect('family1', copy.family); + expect('model1', copy.model); + expect('modelId1', copy.modelId); + expect('arch1', copy.arch); + expect(2, copy.batteryLevel); + expect(SentryOrientation.portrait, copy.orientation); + expect('manufacturer1', copy.manufacturer); + expect('brand1', copy.brand); + expect(99.2, copy.screenDensity); + expect(99, copy.screenDpi); + expect(true, copy.online); + expect(false, copy.charging); + expect(true, copy.lowMemory); + expect(false, copy.simulator); + expect(12345678, copy.memorySize); + expect(123456, copy.freeMemory); + expect(98765, copy.usableMemory); + expect(12345678, copy.storageSize); + expect(12345678, copy.freeStorage); + expect(987654, copy.externalStorageSize); + expect(987654, copy.externalFreeStorage); + expect(bootTime, copy.bootTime); + expect('Charging', copy.batteryStatus); + expect('Intel i9', copy.cpuDescription); + expect('Tablet', copy.deviceType); + expect('foo_bar_baz', copy.deviceUniqueIdentifier); + expect(8, copy.processorCount); + expect(3.4, copy.processorFrequency); + expect(false, copy.supportsAccelerometer); + expect(false, copy.supportsGyroscope); + expect(false, copy.supportsAudio); + expect(false, copy.supportsLocationService); + expect(false, copy.supportsVibration); + expect(2, copy.screenHeightPixels); + expect(2, copy.screenWidthPixels); + }); + }); +} + +SentryDevice _generate({DateTime? testBootTime}) => SentryDevice( + name: 'name', + family: 'family', + model: 'model', + modelId: 'modelId', + arch: 'arch', + batteryLevel: 1, + orientation: SentryOrientation.landscape, + manufacturer: 'manufacturer', + brand: 'brand', + screenHeightPixels: 600, + screenWidthPixels: 800, + screenDensity: 99.1, + screenDpi: 100, + online: false, + charging: true, + lowMemory: false, + simulator: true, + memorySize: 1234567, + freeMemory: 12345, + usableMemory: 9876, + storageSize: 1234567, + freeStorage: 1234567, + externalStorageSize: 98765, + externalFreeStorage: 98765, + bootTime: testBootTime ?? DateTime.now(), + ); diff --git a/packages/dart/test/protocol/sentry_exception_test.dart b/packages/dart/test/protocol/sentry_exception_test.dart new file mode 100644 index 0000000000..7b2e4e0b08 --- /dev/null +++ b/packages/dart/test/protocol/sentry_exception_test.dart @@ -0,0 +1,158 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final sentryException = SentryException( + type: 'type', + value: 'value', + module: 'module', + stackTrace: SentryStackTrace(frames: [SentryStackFrame(absPath: 'abs')]), + mechanism: Mechanism(type: 'type'), + threadId: 1, + unknown: testUnknown, + ); + + final sentryExceptionJson = { + 'type': 'type', + 'value': 'value', + 'module': 'module', + 'stacktrace': { + 'frames': [ + {'abs_path': 'abs'} + ] + }, + 'mechanism': {'type': 'type'}, + 'thread_id': 1, + }; + sentryExceptionJson.addAll(testUnknown); + + group('json', () { + test('fromJson', () { + final sentryException = SentryException.fromJson(sentryExceptionJson); + final json = sentryException.toJson(); + + expect( + DeepCollectionEquality().equals(sentryExceptionJson, json), + true, + ); + }); + + test('should serialize stacktrace', () { + final mechanism = Mechanism( + type: 'mechanism-example', + description: 'a mechanism', + handled: true, + synthetic: false, + helpLink: 'https://help.com', + data: {'polyfill': 'bluebird'}, + meta: { + 'signal': { + 'number': 10, + 'code': 0, + 'name': 'SIGBUS', + 'code_name': 'BUS_NOOP' + } + }, + ); + final stacktrace = SentryStackTrace(frames: [ + SentryStackFrame( + absPath: 'frame-path', + fileName: 'example.dart', + function: 'parse', + module: 'example-module', + lineNo: 1, + colNo: 2, + contextLine: 'context-line example', + inApp: true, + package: 'example-package', + native: false, + platform: 'dart', + rawFunction: 'example-rawFunction', + framesOmitted: [1, 2, 3], + ), + ]); + + final sentryException = SentryException( + type: 'StateError', + value: 'Bad state: error', + module: 'example.module', + stackTrace: stacktrace, + mechanism: mechanism, + threadId: 123456, + ); + + final serialized = sentryException.toJson(); + + expect(serialized['type'], 'StateError'); + expect(serialized['value'], 'Bad state: error'); + expect(serialized['module'], 'example.module'); + expect(serialized['thread_id'], 123456); + expect(serialized['mechanism']['type'], 'mechanism-example'); + expect(serialized['mechanism']['description'], 'a mechanism'); + expect(serialized['mechanism']['handled'], true); + expect(serialized['mechanism']['synthetic'], false); + expect(serialized['mechanism']['help_link'], 'https://help.com'); + expect(serialized['mechanism']['data'], {'polyfill': 'bluebird'}); + expect(serialized['mechanism']['meta'], { + 'signal': { + 'number': 10, + 'code': 0, + 'name': 'SIGBUS', + 'code_name': 'BUS_NOOP' + } + }); + + final serializedFrame = serialized['stacktrace']['frames'].first; + expect(serializedFrame['abs_path'], 'frame-path'); + expect(serializedFrame['filename'], 'example.dart'); + expect(serializedFrame['function'], 'parse'); + expect(serializedFrame['module'], 'example-module'); + expect(serializedFrame['lineno'], 1); + expect(serializedFrame['colno'], 2); + expect(serializedFrame['context_line'], 'context-line example'); + expect(serializedFrame['in_app'], true); + expect(serializedFrame['package'], 'example-package'); + expect(serializedFrame['native'], false); + expect(serializedFrame['platform'], 'dart'); + expect(serializedFrame['raw_function'], 'example-rawFunction'); + expect(serializedFrame['frames_omitted'], [1, 2, 3]); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryException; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect(data.toJson(), copy.toJson()); + }); + + test('copyWith takes new values', () { + final data = sentryException; + + final stackTrace = + SentryStackTrace(frames: [SentryStackFrame(absPath: 'abs1')]); + final mechanism = Mechanism(type: 'type1'); + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + type: 'type1', + value: 'value1', + module: 'module1', + stackTrace: stackTrace, + mechanism: mechanism, + threadId: 2, + ); + + expect('type1', copy.type); + expect('value1', copy.value); + expect('module1', copy.module); + expect(2, copy.threadId); + expect(mechanism.toJson(), copy.mechanism!.toJson()); + expect(stackTrace.toJson(), copy.stackTrace!.toJson()); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_feature_flag_tests.dart b/packages/dart/test/protocol/sentry_feature_flag_tests.dart new file mode 100644 index 0000000000..272c6763ce --- /dev/null +++ b/packages/dart/test/protocol/sentry_feature_flag_tests.dart @@ -0,0 +1,38 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final featureFlag = SentryFeatureFlag( + flag: 'feature_flag_1', + result: true, + unknown: testUnknown, + ); + final featureFlagJson = { + ...testUnknown, + 'flag': 'feature_flag_1', + 'result': true, + }; + + group('json', () { + test('toJson', () { + final json = featureFlag.toJson(); + expect( + DeepCollectionEquality().equals(featureFlagJson, json), + true, + ); + }); + + test('fromJson', () { + final featureFlag = SentryFeatureFlag.fromJson(featureFlagJson); + final json = featureFlag.toJson(); + + expect( + DeepCollectionEquality().equals(featureFlagJson, json), + true, + ); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_feature_flags_tests.dart b/packages/dart/test/protocol/sentry_feature_flags_tests.dart new file mode 100644 index 0000000000..63c6712508 --- /dev/null +++ b/packages/dart/test/protocol/sentry_feature_flags_tests.dart @@ -0,0 +1,42 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final featureFlags = SentryFeatureFlags( + values: [ + SentryFeatureFlag(flag: 'feature_flag_1', result: true), + SentryFeatureFlag(flag: 'feature_flag_2', result: false), + ], + unknown: testUnknown, + ); + final featureFlagsJson = { + ...testUnknown, + 'values': [ + {'name': 'feature_flag_1', 'value': true}, + {'name': 'feature_flag_2', 'value': false}, + ], + }; + + group('json', () { + test('toJson', () { + final json = featureFlags.toJson(); + expect( + DeepCollectionEquality().equals(featureFlagsJson, json), + true, + ); + }); + + test('fromJson', () { + final featureFlags = SentryFeatureFlags.fromJson(featureFlagsJson); + final json = featureFlags.toJson(); + + expect( + DeepCollectionEquality().equals(featureFlagsJson, json), + true, + ); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_feedback_test.dart b/packages/dart/test/protocol/sentry_feedback_test.dart new file mode 100644 index 0000000000..28640aa09e --- /dev/null +++ b/packages/dart/test/protocol/sentry_feedback_test.dart @@ -0,0 +1,85 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final associatedEventId = SentryId.fromId('8a32c0f9be1d34a5efb2c4a10d80de9a'); + + final feedback = SentryFeedback( + message: 'fixture-message', + contactEmail: 'fixture-contactEmail', + name: 'fixture-name', + replayId: 'fixture-replayId', + url: "https://fixture-url.com", + associatedEventId: associatedEventId, + unknown: testUnknown, + ); + + final feedbackJson = { + 'message': 'fixture-message', + 'contact_email': 'fixture-contactEmail', + 'name': 'fixture-name', + 'replay_id': 'fixture-replayId', + 'url': 'https://fixture-url.com', + 'associated_event_id': '8a32c0f9be1d34a5efb2c4a10d80de9a', + }; + feedbackJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = feedback.toJson(); + + expect( + MapEquality().equals(feedbackJson, json), + true, + ); + }); + test('fromJson', () { + final feedback = SentryFeedback.fromJson(feedbackJson); + final json = feedback.toJson(); + + print(feedback); + print(json); + + expect( + MapEquality().equals(feedbackJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = feedback; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + test('copyWith takes new values', () { + final data = feedback; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + message: 'fixture-2-message', + contactEmail: 'fixture-2-contactEmail', + name: 'fixture-2-name', + replayId: 'fixture-2-replayId', + url: "https://fixture-2-url.com", + associatedEventId: SentryId.fromId('1d49af08b6e2c437f9052b1ecfd83dca'), + ); + + expect(copy.message, 'fixture-2-message'); + expect(copy.contactEmail, 'fixture-2-contactEmail'); + expect(copy.name, 'fixture-2-name'); + expect(copy.replayId, 'fixture-2-replayId'); + expect(copy.url, "https://fixture-2-url.com"); + expect(copy.associatedEventId.toString(), + '1d49af08b6e2c437f9052b1ecfd83dca'); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_gpu_test.dart b/packages/dart/test/protocol/sentry_gpu_test.dart new file mode 100644 index 0000000000..44c4b662c7 --- /dev/null +++ b/packages/dart/test/protocol/sentry_gpu_test.dart @@ -0,0 +1,91 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final sentryGpu = SentryGpu( + name: 'fixture-name', + id: 1, + vendorId: '2', + vendorName: 'fixture-vendorName', + memorySize: 3, + apiType: 'fixture-apiType', + multiThreadedRendering: true, + version: '4', + npotSupport: 'fixture-npotSupport', + unknown: testUnknown, + ); + + final sentryGpuJson = { + 'name': 'fixture-name', + 'id': 1, + 'vendor_id': '2', + 'vendor_name': 'fixture-vendorName', + 'memory_size': 3, + 'api_type': 'fixture-apiType', + 'multi_threaded_rendering': true, + 'version': '4', + 'npot_support': 'fixture-npotSupport' + }; + sentryGpuJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = sentryGpu.toJson(); + + expect( + MapEquality().equals(sentryGpuJson, json), + true, + ); + }); + test('fromJson', () { + final sentryGpu = SentryGpu.fromJson(sentryGpuJson); + final json = sentryGpu.toJson(); + + expect( + MapEquality().equals(sentryGpuJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryGpu; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + test('copyWith takes new values', () { + final data = sentryGpu; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + name: 'name1', + id: 11, + vendorId: '22', + vendorName: 'vendorName1', + memorySize: 33, + apiType: 'apiType1', + multiThreadedRendering: false, + version: 'version1', + npotSupport: 'npotSupport1', + ); + + expect('name1', copy.name); + expect(11, copy.id); + expect('22', copy.vendorId); + expect('vendorName1', copy.vendorName); + expect(33, copy.memorySize); + expect('apiType1', copy.apiType); + expect(false, copy.multiThreadedRendering); + expect('version1', copy.version); + expect('npotSupport1', copy.npotSupport); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_log_attribute_test.dart b/packages/dart/test/protocol/sentry_log_attribute_test.dart new file mode 100644 index 0000000000..2c0fb7ce31 --- /dev/null +++ b/packages/dart/test/protocol/sentry_log_attribute_test.dart @@ -0,0 +1,42 @@ +import 'package:test/test.dart'; +import 'package:sentry/sentry.dart'; + +void main() { + test('$SentryLogAttribute string to json', () { + final attribute = SentryLogAttribute.string('test'); + final json = attribute.toJson(); + expect(json, { + 'value': 'test', + 'type': 'string', + }); + }); + + test('$SentryLogAttribute bool to json', () { + final attribute = SentryLogAttribute.bool(true); + final json = attribute.toJson(); + expect(json, { + 'value': true, + 'type': 'boolean', + }); + }); + + test('$SentryLogAttribute int to json', () { + final attribute = SentryLogAttribute.int(1); + final json = attribute.toJson(); + + expect(json, { + 'value': 1, + 'type': 'integer', + }); + }); + + test('$SentryLogAttribute double to json', () { + final attribute = SentryLogAttribute.double(1.0); + final json = attribute.toJson(); + + expect(json, { + 'value': 1.0, + 'type': 'double', + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_log_test.dart b/packages/dart/test/protocol/sentry_log_test.dart new file mode 100644 index 0000000000..0921df7a32 --- /dev/null +++ b/packages/dart/test/protocol/sentry_log_test.dart @@ -0,0 +1,86 @@ +import 'package:test/test.dart'; +import 'package:sentry/sentry.dart'; + +void main() { + test('$SentryLog to json', () { + final timestamp = DateTime.now(); + final traceId = SentryId.newId(); + + final logItem = SentryLog( + timestamp: timestamp, + traceId: traceId, + level: SentryLogLevel.info, + body: 'fixture-body', + attributes: { + 'test': SentryLogAttribute.string('fixture-test'), + 'test2': SentryLogAttribute.bool(true), + 'test3': SentryLogAttribute.int(9001), + 'test4': SentryLogAttribute.double(9000.1), + }, + severityNumber: 1, + ); + + final json = logItem.toJson(); + + expect(json, { + 'timestamp': timestamp.toIso8601String(), + 'trace_id': traceId.toString(), + 'level': 'info', + 'body': 'fixture-body', + 'attributes': { + 'test': { + 'value': 'fixture-test', + 'type': 'string', + }, + 'test2': { + 'value': true, + 'type': 'boolean', + }, + 'test3': { + 'value': 9001, + 'type': 'integer', + }, + 'test4': { + 'value': 9000.1, + 'type': 'double', + }, + }, + 'severity_number': 1, + }); + }); + + test('$SentryLevel without severity number infers from level in toJson', () { + final logItem = SentryLog( + timestamp: DateTime.now(), + traceId: SentryId.newId(), + level: SentryLogLevel.trace, + body: 'fixture-body', + attributes: { + 'test': SentryLogAttribute.string('fixture-test'), + }, + ); + + var json = logItem.toJson(); + expect(json['severity_number'], 1); + + logItem.level = SentryLogLevel.debug; + json = logItem.toJson(); + expect(json['severity_number'], 5); + + logItem.level = SentryLogLevel.info; + json = logItem.toJson(); + expect(json['severity_number'], 9); + + logItem.level = SentryLogLevel.warn; + json = logItem.toJson(); + expect(json['severity_number'], 13); + + logItem.level = SentryLogLevel.error; + json = logItem.toJson(); + expect(json['severity_number'], 17); + + logItem.level = SentryLogLevel.fatal; + json = logItem.toJson(); + expect(json['severity_number'], 21); + }); +} diff --git a/packages/dart/test/protocol/sentry_message_test.dart b/packages/dart/test/protocol/sentry_message_test.dart new file mode 100644 index 0000000000..d7d9260a8e --- /dev/null +++ b/packages/dart/test/protocol/sentry_message_test.dart @@ -0,0 +1,68 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final sentryMessage = SentryMessage( + 'message 1', + template: 'message %d', + params: ['1'], + unknown: testUnknown, + ); + + final sentryMessageJson = { + 'formatted': 'message 1', + 'message': 'message %d', + 'params': ['1'], + }; + sentryMessageJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = sentryMessage.toJson(); + + expect( + DeepCollectionEquality().equals(sentryMessageJson, json), + true, + ); + }); + test('fromJson', () { + final sentryMessage = SentryMessage.fromJson(sentryMessageJson); + final json = sentryMessage.toJson(); + + expect( + DeepCollectionEquality().equals(sentryMessageJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryMessage; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryMessage; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + formatted: 'message 21', + template: 'message 2 %d', + params: ['2'], + ); + + expect('message 21', copy.formatted); + expect('message 2 %d', copy.template); + expect(['2'], copy.params); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_operating_system_test.dart b/packages/dart/test/protocol/sentry_operating_system_test.dart new file mode 100644 index 0000000000..5054642f81 --- /dev/null +++ b/packages/dart/test/protocol/sentry_operating_system_test.dart @@ -0,0 +1,79 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final sentryOperatingSystem = SentryOperatingSystem( + name: 'fixture-name', + version: 'fixture-version', + build: 'fixture-build', + kernelVersion: 'fixture-kernelVersion', + rooted: true, + rawDescription: 'fixture-rawDescription', + unknown: testUnknown, + ); + + final sentryOperatingSystemJson = { + 'name': 'fixture-name', + 'version': 'fixture-version', + 'build': 'fixture-build', + 'kernel_version': 'fixture-kernelVersion', + 'rooted': true, + 'raw_description': 'fixture-rawDescription' + }; + sentryOperatingSystemJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = sentryOperatingSystem.toJson(); + + expect( + MapEquality().equals(sentryOperatingSystemJson, json), + true, + ); + }); + test('fromJson', () { + final sentryOperatingSystem = + SentryOperatingSystem.fromJson(sentryOperatingSystemJson); + final json = sentryOperatingSystem.toJson(); + + expect( + MapEquality().equals(sentryOperatingSystemJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryOperatingSystem; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryOperatingSystem; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + name: 'name1', + version: 'version1', + build: 'build1', + kernelVersion: 'kernelVersion1', + rooted: true, + ); + + expect('name1', copy.name); + expect('version1', copy.version); + expect('build1', copy.build); + expect('kernelVersion1', copy.kernelVersion); + expect(true, copy.rooted); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_package_test.dart b/packages/dart/test/protocol/sentry_package_test.dart new file mode 100644 index 0000000000..8ec75db548 --- /dev/null +++ b/packages/dart/test/protocol/sentry_package_test.dart @@ -0,0 +1,63 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final sentryPackage = SentryPackage( + 'name', + 'version', + unknown: testUnknown, + ); + + final sentryPackageJson = { + 'name': 'name', + 'version': 'version', + }; + sentryPackageJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = sentryPackage.toJson(); + + expect( + MapEquality().equals(sentryPackageJson, json), + true, + ); + }); + test('fromJson', () { + final sentryPackage = SdkVersion.fromJson(sentryPackageJson); + final json = sentryPackage.toJson(); + + expect( + MapEquality().equals(sentryPackageJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryPackage; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + test('copyWith takes new values', () { + final data = sentryPackage; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + name: 'name1', + version: 'version1', + ); + + expect('name1', copy.name); + expect('version1', copy.version); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_proxy_test.dart b/packages/dart/test/protocol/sentry_proxy_test.dart new file mode 100644 index 0000000000..4aefef09b3 --- /dev/null +++ b/packages/dart/test/protocol/sentry_proxy_test.dart @@ -0,0 +1,102 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final proxy = SentryProxy( + host: 'localhost', + port: 8080, + type: SentryProxyType.http, + user: 'admin', + pass: '0000', + ); + + final proxyJson = { + 'host': 'localhost', + 'port': 8080, + 'type': 'HTTP', + 'user': 'admin', + 'pass': '0000', + }; + + group('toPacString', () { + test('returns "DIRECT" for ProxyType.direct', () { + SentryProxy proxy = SentryProxy(type: SentryProxyType.direct); + expect(proxy.toPacString(), equals('DIRECT')); + }); + + test('returns "PROXY host:port" for ProxyType.http with host and port', () { + SentryProxy proxy = SentryProxy( + type: SentryProxyType.http, host: 'localhost', port: 8080); + expect(proxy.toPacString(), equals('PROXY localhost:8080')); + }); + + test('returns "PROXY host" for ProxyType.http with host only', () { + SentryProxy proxy = + SentryProxy(type: SentryProxyType.http, host: 'localhost'); + expect(proxy.toPacString(), equals('PROXY localhost')); + }); + + test('returns "SOCKS host:port" for ProxyType.socks with host and port', + () { + SentryProxy proxy = SentryProxy( + type: SentryProxyType.socks, host: 'localhost', port: 8080); + expect(proxy.toPacString(), equals('SOCKS localhost:8080')); + }); + + test('returns "SOCKS host" for ProxyType.socks with host only', () { + SentryProxy proxy = + SentryProxy(type: SentryProxyType.socks, host: 'localhost'); + expect(proxy.toPacString(), equals('SOCKS localhost')); + }); + + test('falls back to "DIRECT" if http is missing host', () { + SentryProxy proxy = SentryProxy(type: SentryProxyType.http); + expect(proxy.toPacString(), equals('DIRECT')); + }); + + test('falls back to "DIRECT" if socks is missing host', () { + SentryProxy proxy = SentryProxy(type: SentryProxyType.socks); + expect(proxy.toPacString(), equals('DIRECT')); + }); + }); + + group('json', () { + test('toJson', () { + final json = proxy.toJson(); + + expect( + DeepCollectionEquality().equals(proxyJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = proxy; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect(data.toJson(), copy.toJson()); + }); + + test('copyWith takes new values', () { + final data = proxy; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + host: 'localhost-2', + port: 9001, + type: SentryProxyType.socks, + user: 'user', + pass: '1234', + ); + + expect('localhost-2', copy.host); + expect(9001, copy.port); + expect(SentryProxyType.socks, copy.type); + expect('user', copy.user); + expect('1234', copy.pass); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_request_test.dart b/packages/dart/test/protocol/sentry_request_test.dart new file mode 100644 index 0000000000..ee1d65d290 --- /dev/null +++ b/packages/dart/test/protocol/sentry_request_test.dart @@ -0,0 +1,92 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final sentryRequest = SentryRequest( + url: 'url', + method: 'method', + queryString: 'queryString', + cookies: 'cookies', + data: {'key': 'value'}, + headers: {'header_key': 'header_value'}, + env: {'env_key': 'env_value'}, + apiTarget: 'GraphQL', + unknown: testUnknown, + ); + + final sentryRequestJson = { + 'url': 'url', + 'method': 'method', + 'query_string': 'queryString', + 'cookies': 'cookies', + 'data': {'key': 'value'}, + 'headers': {'header_key': 'header_value'}, + 'env': {'env_key': 'env_value'}, + 'api_target': 'GraphQL', + }; + sentryRequestJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = sentryRequest.toJson(); + + expect( + DeepCollectionEquality().equals(sentryRequestJson, json), + true, + ); + }); + test('fromJson', () { + final sentryRequest = SentryRequest.fromJson(sentryRequestJson); + final json = sentryRequest.toJson(); + + expect( + DeepCollectionEquality().equals(sentryRequestJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryRequest; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + DeepCollectionEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryRequest; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + url: 'url1', + method: 'method1', + queryString: 'queryString1', + cookies: 'cookies1', + data: {'key1': 'value1'}, + ); + + expect('url1', copy.url); + expect('method1', copy.method); + expect('queryString1', copy.queryString); + expect('cookies1', copy.cookies); + expect({'key1': 'value1'}, copy.data); + }); + }); + + test('SentryRequest.fromUri', () { + final request = SentryRequest.fromUri( + uri: Uri.parse('https://example.org/foo/bar?key=value#fragment'), + ); + + expect(request.url, 'https://example.org/foo/bar'); + expect(request.fragment, 'fragment'); + expect(request.queryString, 'key=value'); + }); +} diff --git a/packages/dart/test/protocol/sentry_response_test.dart b/packages/dart/test/protocol/sentry_response_test.dart new file mode 100644 index 0000000000..216cca190f --- /dev/null +++ b/packages/dart/test/protocol/sentry_response_test.dart @@ -0,0 +1,64 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final sentryResponse = SentryResponse( + bodySize: 42, + statusCode: 200, + headers: {'header_key': 'header_value'}, + cookies: 'foo=bar, another=cookie', + data: 'foo', + ); + + final sentryResponseJson = { + 'body_size': 42, + 'status_code': 200, + 'headers': {'header_key': 'header_value'}, + 'cookies': 'foo=bar, another=cookie', + 'data': 'foo', + }; + + group('json', () { + test('toJson', () { + final json = sentryResponse.toJson(); + + expect( + DeepCollectionEquality().equals(sentryResponseJson, json), + true, + ); + }); + test('fromJson', () { + final sentryResponse = SentryResponse.fromJson(sentryResponseJson); + final json = sentryResponse.toJson(); + + expect( + DeepCollectionEquality().equals(sentryResponseJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryResponse; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + expect(data.toJson(), copy.toJson()); + }); + + test('copyWith takes new values', () { + final data = sentryResponse; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + bodySize: 11, + headers: {'key1': 'value1'}, + statusCode: 301, + ); + + expect(11, copy.bodySize); + expect({'key1': 'value1'}, copy.headers); + expect(301, copy.statusCode); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_runtime_test.dart b/packages/dart/test/protocol/sentry_runtime_test.dart new file mode 100644 index 0000000000..852738de42 --- /dev/null +++ b/packages/dart/test/protocol/sentry_runtime_test.dart @@ -0,0 +1,71 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final sentryRuntime = SentryRuntime( + key: 'key', + name: 'name', + version: 'version', + rawDescription: 'rawDescription', + unknown: testUnknown, + ); + + final sentryRuntimeJson = { + 'name': 'name', + 'version': 'version', + 'raw_description': 'rawDescription', + }; + sentryRuntimeJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = sentryRuntime.toJson(); + + expect( + MapEquality().equals(sentryRuntimeJson, json), + true, + ); + }); + test('fromJson', () { + final sentryRuntime = SentryRuntime.fromJson(sentryRuntimeJson); + final json = sentryRuntime.toJson(); + + expect( + MapEquality().equals(sentryRuntimeJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryRuntime; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryRuntime; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + key: 'key1', + name: 'name1', + version: 'version1', + rawDescription: 'rawDescription1', + ); + + expect('key1', copy.key); + expect('name1', copy.name); + expect('version1', copy.version); + expect('rawDescription1', copy.rawDescription); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_stack_frame_test.dart b/packages/dart/test/protocol/sentry_stack_frame_test.dart new file mode 100644 index 0000000000..7955b31053 --- /dev/null +++ b/packages/dart/test/protocol/sentry_stack_frame_test.dart @@ -0,0 +1,127 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final sentryStackFrame = SentryStackFrame( + absPath: 'absPath', + fileName: 'fileName', + function: 'function', + module: 'module', + lineNo: 1, + colNo: 2, + contextLine: 'contextLine', + inApp: true, + package: 'package', + native: false, + platform: 'platform', + imageAddr: 'imageAddr', + symbolAddr: 'symbolAddr', + instructionAddr: 'instructionAddr', + rawFunction: 'rawFunction', + framesOmitted: [1], + preContext: ['a'], + postContext: ['b'], + vars: {'key': 'value'}, + unknown: testUnknown, + ); + + final sentryStackFrameJson = { + 'pre_context': ['a'], + 'post_context': ['b'], + 'vars': {'key': 'value'}, + 'frames_omitted': [1], + 'filename': 'fileName', + 'package': 'package', + 'function': 'function', + 'module': 'module', + 'lineno': 1, + 'colno': 2, + 'abs_path': 'absPath', + 'context_line': 'contextLine', + 'in_app': true, + 'native': false, + 'platform': 'platform', + 'image_addr': 'imageAddr', + 'symbol_addr': 'symbolAddr', + 'instruction_addr': 'instructionAddr', + 'raw_function': 'rawFunction', + }; + sentryStackFrameJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = sentryStackFrame.toJson(); + + expect( + DeepCollectionEquality().equals(sentryStackFrameJson, json), + true, + ); + }); + test('fromJson', () { + final sentryStackFrame = SentryStackFrame.fromJson(sentryStackFrameJson); + final json = sentryStackFrame.toJson(); + + expect( + DeepCollectionEquality().equals(sentryStackFrameJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryStackFrame; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect(data.toJson(), copy.toJson()); + }); + test('copyWith takes new values', () { + final data = sentryStackFrame; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + absPath: 'absPath1', + fileName: 'fileName1', + function: 'function1', + module: 'module1', + lineNo: 11, + colNo: 22, + contextLine: 'contextLine1', + inApp: false, + package: 'package1', + native: true, + platform: 'platform1', + imageAddr: 'imageAddr1', + symbolAddr: 'symbolAddr1', + instructionAddr: 'instructionAddr1', + rawFunction: 'rawFunction1', + framesOmitted: [11], + preContext: ['ab'], + postContext: ['bb'], + vars: {'key1': 'value1'}, + ); + + expect('absPath1', copy.absPath); + expect('fileName1', copy.fileName); + expect('function1', copy.function); + expect('module1', copy.module); + expect(11, copy.lineNo); + expect(22, copy.colNo); + expect(false, copy.inApp); + expect('package1', copy.package); + expect(true, copy.native); + expect('platform1', copy.platform); + expect('imageAddr1', copy.imageAddr); + expect('symbolAddr1', copy.symbolAddr); + expect('instructionAddr1', copy.instructionAddr); + expect('rawFunction1', copy.rawFunction); + expect([11], copy.framesOmitted); + expect(['ab'], copy.preContext); + expect(['bb'], copy.postContext); + expect({'key1': 'value1'}, copy.vars); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_stack_trace_test.dart b/packages/dart/test/protocol/sentry_stack_trace_test.dart new file mode 100644 index 0000000000..b8269f3bbb --- /dev/null +++ b/packages/dart/test/protocol/sentry_stack_trace_test.dart @@ -0,0 +1,76 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final sentryStackTrace = SentryStackTrace( + frames: [SentryStackFrame(absPath: 'abs')], + registers: {'key': 'value'}, + lang: 'de', + snapshot: true, + unknown: testUnknown, + ); + + final sentryStackTraceJson = { + 'frames': [ + {'abs_path': 'abs'} + ], + 'registers': {'key': 'value'}, + 'lang': 'de', + 'snapshot': true, + }; + sentryStackTraceJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = sentryStackTrace.toJson(); + + expect( + DeepCollectionEquality().equals(sentryStackTraceJson, json), + true, + ); + }); + test('fromJson', () { + final sentryStackTrace = SentryStackTrace.fromJson(sentryStackTraceJson); + final json = sentryStackTrace.toJson(); + + expect( + DeepCollectionEquality().equals(sentryStackTraceJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryStackTrace; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect(data.toJson(), copy.toJson()); + }); + + test('copyWith takes new values', () { + final data = sentryStackTrace; + + final frames = [SentryStackFrame(absPath: 'abs1')]; + final registers = {'key1': 'value1'}; +// ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + frames: frames, + registers: registers, + ); + + expect( + ListEquality().equals(frames, copy.frames), + true, + ); + expect( + MapEquality().equals(registers, copy.registers), + true, + ); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_trace_header_test.dart b/packages/dart/test/protocol/sentry_trace_header_test.dart new file mode 100644 index 0000000000..0743c03da8 --- /dev/null +++ b/packages/dart/test/protocol/sentry_trace_header_test.dart @@ -0,0 +1,41 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final _traceId = SentryId.newId(); + final _spanId = SpanId.newId(); + test('header adds 1 to sampled', () { + final header = SentryTraceHeader(_traceId, _spanId, sampled: true); + + expect(header.value, '$_traceId-$_spanId-1'); + }); + + test('header adds 0 to not sampled', () { + final header = SentryTraceHeader(_traceId, _spanId, sampled: false); + + expect(header.value, '$_traceId-$_spanId-0'); + }); + + test('header does not add sampled if no sampled decision', () { + final header = SentryTraceHeader(_traceId, _spanId); + + expect(header.value, '$_traceId-$_spanId'); + }); + + test('header return its name', () { + final header = SentryTraceHeader(_traceId, _spanId); + + expect(header.name, 'sentry-trace'); + }); + + test('invalid header throws $InvalidSentryTraceHeaderException', () { + Object? exception; + try { + SentryTraceHeader.fromTraceHeader('invalidHeader'); + } catch (error) { + exception = error; + } + + expect(exception is InvalidSentryTraceHeaderException, true); + }); +} diff --git a/packages/dart/test/protocol/sentry_transaction_info_test.dart b/packages/dart/test/protocol/sentry_transaction_info_test.dart new file mode 100644 index 0000000000..31438d820c --- /dev/null +++ b/packages/dart/test/protocol/sentry_transaction_info_test.dart @@ -0,0 +1,29 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + group('$SentryTransactionInfo', () { + final info = SentryTransactionInfo( + 'component', + unknown: testUnknown, + ); + + final json = {'source': 'component'}; + json.addAll(testUnknown); + + test('returns source', () { + expect(info.source, 'component'); + }); + + test('toJson has source', () { + expect(info.toJson(), json); + }); + + test('fromJson has source', () { + final info = SentryTransactionInfo.fromJson({'source': 'component'}); + expect(info.source, 'component'); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_user_test.dart b/packages/dart/test/protocol/sentry_user_test.dart new file mode 100644 index 0000000000..6079e9be32 --- /dev/null +++ b/packages/dart/test/protocol/sentry_user_test.dart @@ -0,0 +1,98 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; + +void main() { + final sentryUser = SentryUser( + id: 'id', + username: 'username', + email: 'email', + ipAddress: 'ipAddress', + data: {'key': 'value'}, + unknown: testUnknown, + ); + + final sentryUserJson = { + 'id': 'id', + 'username': 'username', + 'email': 'email', + 'ip_address': 'ipAddress', + 'data': {'key': 'value'}, + }; + sentryUserJson.addAll(testUnknown); + + group('json', () { + test('toJson', () { + final json = sentryUser.toJson(); + + print("$json"); + + expect( + DeepCollectionEquality().equals(sentryUserJson, json), + true, + ); + }); + test('fromJson', () { + final sentryUser = SentryUser.fromJson(sentryUserJson); + final json = sentryUser.toJson(); + + expect( + DeepCollectionEquality().equals(sentryUserJson, json), + true, + ); + }); + + test('toJson only serialises non-null values', () { + var data = SentryUser(id: 'id'); + + var json = data.toJson(); + + expect(json.containsKey('id'), true); + expect(json.containsKey('username'), false); + expect(json.containsKey('email'), false); + expect(json.containsKey('ip_address'), false); + expect(json.containsKey('extras'), false); + + data = SentryUser(ipAddress: 'ip'); + + json = data.toJson(); + + expect(json.containsKey('id'), false); + expect(json.containsKey('username'), false); + expect(json.containsKey('email'), false); + expect(json.containsKey('ip_address'), true); + expect(json.containsKey('extras'), false); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryUser; + + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith(); + + expect(data.toJson(), copy.toJson()); + }); + + test('copyWith takes new values', () { + final data = sentryUser; + // ignore: deprecated_member_use_from_same_package + final copy = data.copyWith( + id: 'id1', + username: 'username1', + email: 'email1', + ipAddress: 'ipAddress1', + data: {'key1': 'value1'}, + ); + + expect('id1', copy.id); + expect('username1', copy.username); + expect('email1', copy.email); + expect('ipAddress1', copy.ipAddress); + expect({'key1': 'value1'}, copy.data); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_view_hierarchy_element_test.dart b/packages/dart/test/protocol/sentry_view_hierarchy_element_test.dart new file mode 100644 index 0000000000..1d6e753acd --- /dev/null +++ b/packages/dart/test/protocol/sentry_view_hierarchy_element_test.dart @@ -0,0 +1,65 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + group('json', () { + test('toJson with children', () { + final element = SentryViewHierarchyElement( + 'RenderObjectToWidgetAdapter', + depth: 1, + identifier: 'RenderView#a2216', + width: 100, + height: 200, + x: 100, + y: 50, + z: 30, + visible: true, + alpha: 90, + extra: {'key': 'value'}, + ); + final element2 = SentryViewHierarchyElement( + 'SentryScreenshotWidget', + depth: 2, + ); + element.children.add(element2); + + final map = element.toJson(); + + expect(map, { + 'type': 'RenderObjectToWidgetAdapter', + 'depth': 1, + 'identifier': 'RenderView#a2216', + 'children': [ + { + 'type': 'SentryScreenshotWidget', + 'depth': 2, + }, + ], + 'width': 100, + 'height': 200, + 'x': 100, + 'y': 50, + 'z': 30, + 'visible': true, + 'alpha': 90, + 'key': 'value', + }); + }); + + test('toJson no children', () { + final element = SentryViewHierarchyElement( + 'RenderObjectToWidgetAdapter', + depth: 1, + identifier: 'RenderView#a2216', + ); + + final map = element.toJson(); + + expect(map, { + 'type': 'RenderObjectToWidgetAdapter', + 'depth': 1, + 'identifier': 'RenderView#a2216', + }); + }); + }); +} diff --git a/packages/dart/test/protocol/sentry_view_hierarchy_test.dart b/packages/dart/test/protocol/sentry_view_hierarchy_test.dart new file mode 100644 index 0000000000..cde2124682 --- /dev/null +++ b/packages/dart/test/protocol/sentry_view_hierarchy_test.dart @@ -0,0 +1,52 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + group('json', () { + test('toJson with children', () { + final element = SentryViewHierarchyElement( + 'RenderObjectToWidgetAdapter', + depth: 1, + identifier: 'RenderView#a2216', + ); + + final element2 = SentryViewHierarchyElement( + 'SentryScreenshotWidget', + depth: 2, + ); + element.children.add(element2); + + final viewHierrchy = SentryViewHierarchy('flutter'); + viewHierrchy.windows.add(element); + + final map = viewHierrchy.toJson(); + + expect(map, { + 'rendering_system': 'flutter', + 'windows': [ + { + 'type': 'RenderObjectToWidgetAdapter', + 'depth': 1, + 'identifier': 'RenderView#a2216', + 'children': [ + { + 'type': 'SentryScreenshotWidget', + 'depth': 2, + }, + ] + }, + ], + }); + }); + + test('toJson no children', () { + final viewHierrchy = SentryViewHierarchy('flutter'); + + final map = viewHierrchy.toJson(); + + expect(map, { + 'rendering_system': 'flutter', + }); + }); + }); +} diff --git a/packages/dart/test/protocol/span_id_test.dart b/packages/dart/test/protocol/span_id_test.dart new file mode 100644 index 0000000000..cefecec576 --- /dev/null +++ b/packages/dart/test/protocol/span_id_test.dart @@ -0,0 +1,23 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + test('empty serializes to 0000000000000000', () { + expect(SpanId.empty().toString(), '0000000000000000'); + }); + + test('fromId serializes to 976e0cd945864f60', () { + expect(SpanId.fromId('976e0cd945864f60').toString(), '976e0cd945864f60'); + }); + + test('newId generates new id', () { + expect(SpanId.newId().toString(), isNotNull); + }); + + test('equality check matches for same id', () { + final id1 = SpanId.fromId('976e0cd945864f60'); + final id2 = SpanId.fromId('976e0cd945864f60'); + + expect(id1, id2); + }); +} diff --git a/packages/dart/test/protocol/span_status_test.dart b/packages/dart/test/protocol/span_status_test.dart new file mode 100644 index 0000000000..87588feb07 --- /dev/null +++ b/packages/dart/test/protocol/span_status_test.dart @@ -0,0 +1,138 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + test('SpanStatus ok', () { + expect(SpanStatus.ok().toString(), 'ok'); + }); + + test('SpanStatus cancelled', () { + expect(SpanStatus.cancelled().toString(), 'cancelled'); + }); + + test('SpanStatus internalError', () { + expect(SpanStatus.internalError().toString(), 'internal_error'); + }); + + test('SpanStatus unknown', () { + expect(SpanStatus.unknown().toString(), 'unknown'); + }); + + test('SpanStatus unknownError', () { + expect(SpanStatus.unknownError().toString(), 'unknown_error'); + }); + + test('SpanStatus invalidArgument', () { + expect(SpanStatus.invalidArgument().toString(), 'invalid_argument'); + }); + + test('SpanStatus deadlineExceeded', () { + expect(SpanStatus.deadlineExceeded().toString(), 'deadline_exceeded'); + }); + + test('SpanStatus notFound', () { + expect(SpanStatus.notFound().toString(), 'not_found'); + }); + + test('SpanStatus alreadyExists', () { + expect(SpanStatus.alreadyExists().toString(), 'already_exists'); + }); + + test('SpanStatus permissionDenied', () { + expect(SpanStatus.permissionDenied().toString(), 'permission_denied'); + }); + + test('SpanStatus resourceExhausted', () { + expect(SpanStatus.resourceExhausted().toString(), 'resource_exhausted'); + }); + + test('SpanStatus failedPrecondition', () { + expect(SpanStatus.failedPrecondition().toString(), 'failed_precondition'); + }); + + test('SpanStatus aborted', () { + expect(SpanStatus.aborted().toString(), 'aborted'); + }); + + test('SpanStatus outOfRange', () { + expect(SpanStatus.outOfRange().toString(), 'out_of_range'); + }); + + test('SpanStatus unimplemented', () { + expect(SpanStatus.unimplemented().toString(), 'unimplemented'); + }); + + test('SpanStatus unavailable', () { + expect(SpanStatus.unavailable().toString(), 'unavailable'); + }); + + test('SpanStatus dataLoss', () { + expect(SpanStatus.dataLoss().toString(), 'data_loss'); + }); + + test('SpanStatus unauthenticated', () { + expect(SpanStatus.unauthenticated().toString(), 'unauthenticated'); + }); + + test('fromHttpStatusCode returns ok if 200 to 299', () { + expect(SpanStatus.fromHttpStatusCode(200), SpanStatus.ok()); + expect(SpanStatus.fromHttpStatusCode(299), SpanStatus.ok()); + }); + + test('fromHttpStatusCode returns cancelled if 499', () { + expect(SpanStatus.fromHttpStatusCode(499), SpanStatus.cancelled()); + }); + + test('fromHttpStatusCode returns unknown if 500', () { + expect(SpanStatus.fromHttpStatusCode(500), SpanStatus.unknown()); + }); + + test('fromHttpStatusCode returns invalid argument if 500', () { + expect(SpanStatus.fromHttpStatusCode(400), SpanStatus.invalidArgument()); + }); + + test('fromHttpStatusCode returns invalid argument if 504', () { + expect(SpanStatus.fromHttpStatusCode(504), SpanStatus.deadlineExceeded()); + }); + + test('fromHttpStatusCode returns not found if 404', () { + expect(SpanStatus.fromHttpStatusCode(404), SpanStatus.notFound()); + }); + + test('fromHttpStatusCode returns already exists if 409', () { + expect(SpanStatus.fromHttpStatusCode(409), SpanStatus.alreadyExists()); + }); + + test('fromHttpStatusCode returns permissionDenied if 403', () { + expect(SpanStatus.fromHttpStatusCode(403), SpanStatus.permissionDenied()); + }); + + test('fromHttpStatusCode returns resourceExhausted if 429', () { + expect(SpanStatus.fromHttpStatusCode(429), SpanStatus.resourceExhausted()); + }); + + test('fromHttpStatusCode returns unimplemented if 501', () { + expect(SpanStatus.fromHttpStatusCode(501), SpanStatus.unimplemented()); + }); + + test('fromHttpStatusCode returns unavailable if 503', () { + expect(SpanStatus.fromHttpStatusCode(503), SpanStatus.unavailable()); + }); + + test('fromHttpStatusCode returns unauthenticated if 401', () { + expect(SpanStatus.fromHttpStatusCode(401), SpanStatus.unauthenticated()); + }); + + test('fromHttpStatusCode returns unknownError if not found', () { + expect(SpanStatus.fromHttpStatusCode(100), SpanStatus.unknownError()); + }); + + test('fromHttpStatusCode returns fallback if not found', () { + expect( + SpanStatus.fromHttpStatusCode( + 101, + fallback: SpanStatus.aborted(), + ), + SpanStatus.aborted()); + }); +} diff --git a/packages/dart/test/recursive_exception_cause_extractor_test.dart b/packages/dart/test/recursive_exception_cause_extractor_test.dart new file mode 100644 index 0000000000..9eeaf63697 --- /dev/null +++ b/packages/dart/test/recursive_exception_cause_extractor_test.dart @@ -0,0 +1,189 @@ +import 'package:sentry/src/exception_cause.dart'; +import 'package:sentry/src/exception_cause_extractor.dart'; +import 'package:sentry/src/recursive_exception_cause_extractor.dart'; +import 'package:sentry/src/protocol/mechanism.dart'; +import 'package:sentry/src/throwable_mechanism.dart'; +import 'package:test/test.dart'; +import 'test_utils.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('flatten', () { + final errorC = ExceptionC(); + final errorB = ExceptionB(errorC); + final errorA = ExceptionA(errorB); + + fixture.options.addExceptionCauseExtractor( + ExceptionACauseExtractor(false), + ); + + fixture.options.addExceptionCauseExtractor( + ExceptionBCauseExtractor(), + ); + + final sut = fixture.getSut(); + + final flattened = sut.flatten(errorA, null); + final actual = flattened.map((exceptionCause) => exceptionCause.exception); + expect(actual, [errorA, errorB, errorC]); + }); + + test('parent (source) references', () { + final errorC = ExceptionC(); + final errorB = ExceptionB(errorC); + final errorA = ExceptionA(errorB); + + fixture.options.addExceptionCauseExtractor( + ExceptionACauseExtractor(false), + ); + + fixture.options.addExceptionCauseExtractor( + ExceptionBCauseExtractor(), + ); + + final sut = fixture.getSut(); + + final flattened = sut.flatten(errorA, null); + final actual = + flattened.map((exceptionCause) => exceptionCause.source).toList(); + expect(actual, [null, "other", "anotherOther"]); + }); + + test('flatten breaks circularity', () { + final a = ExceptionCircularA(); + final b = ExceptionCircularB(); + a.other = b; + b.other = a; + + fixture.options.addExceptionCauseExtractor( + ExceptionCircularAExtractor(), + ); + + fixture.options.addExceptionCauseExtractor( + ExceptionCircularBExtractor(), + ); + + final sut = fixture.getSut(); + + final flattened = sut.flatten(a, null); + final actual = flattened.map((exceptionCause) => exceptionCause.exception); + + expect(actual, [a, b]); + }); + + test('flatten preserves throwable mechanism', () { + final errorC = ExceptionC(); + final errorB = ExceptionB(errorC); + final errorA = ExceptionA(errorB); + + fixture.options.addExceptionCauseExtractor( + ExceptionACauseExtractor(false), + ); + + fixture.options.addExceptionCauseExtractor( + ExceptionBCauseExtractor(), + ); + + final mechanism = Mechanism(type: "foo"); + final throwableMechanism = ThrowableMechanism(mechanism, errorA); + + final sut = fixture.getSut(); + final flattened = sut.flatten(throwableMechanism, null); + + final actual = flattened.map((exceptionCause) => exceptionCause.exception); + expect(actual, [throwableMechanism, errorB, errorC]); + }); + + test('throw during extractions is handled', () { + final errorB = ExceptionB(null); + final errorA = ExceptionA(errorB); + + fixture.options.addExceptionCauseExtractor( + ExceptionACauseExtractor(true), + ); + + fixture.options.addExceptionCauseExtractor( + ExceptionBCauseExtractor(), + ); + + fixture.options.automatedTestMode = false; + final sut = fixture.getSut(); + + final flattened = sut.flatten(errorA, null); + final actual = flattened.map((exceptionCause) => exceptionCause.exception); + + expect(actual, [errorA]); + }); +} + +class Fixture { + final options = defaultTestOptions(); + + RecursiveExceptionCauseExtractor getSut() { + return RecursiveExceptionCauseExtractor(options); + } +} + +class ExceptionA { + ExceptionA(this.other); + final ExceptionB? other; +} + +class ExceptionB { + ExceptionB(this.anotherOther); + final ExceptionC? anotherOther; +} + +class ExceptionC { + // I am empty inside +} + +class ExceptionACauseExtractor extends ExceptionCauseExtractor { + ExceptionACauseExtractor(this.throwing); + + final bool throwing; + + @override + ExceptionCause? cause(ExceptionA error) { + if (throwing) { + throw StateError("Unexpected exception"); + } + return ExceptionCause(error.other, null, source: "other"); + } +} + +class ExceptionBCauseExtractor extends ExceptionCauseExtractor { + @override + ExceptionCause? cause(ExceptionB error) { + return ExceptionCause(error.anotherOther, null, source: "anotherOther"); + } +} + +class ExceptionCircularA { + ExceptionCircularB? other; +} + +class ExceptionCircularB { + ExceptionCircularA? other; +} + +class ExceptionCircularAExtractor + extends ExceptionCauseExtractor { + @override + ExceptionCause? cause(ExceptionCircularA error) { + return ExceptionCause(error.other, null); + } +} + +class ExceptionCircularBExtractor + extends ExceptionCauseExtractor { + @override + ExceptionCause? cause(ExceptionCircularB error) { + return ExceptionCause(error.other, null); + } +} diff --git a/packages/dart/test/scope_test.dart b/packages/dart/test/scope_test.dart new file mode 100644 index 0000000000..a7824baac5 --- /dev/null +++ b/packages/dart/test/scope_test.dart @@ -0,0 +1,920 @@ +// ignore_for_file: deprecated_member_use_from_same_package + +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; +import 'mocks/mock_hub.dart'; +import 'mocks/mock_scope_observer.dart'; +import 'test_utils.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('sets $SentryLevel', () { + final sut = fixture.getSut(); + + sut.level = SentryLevel.debug; + + expect(sut.level, SentryLevel.debug); + }); + + test('sets transaction', () { + final sut = fixture.getSut(); + + sut.transaction = 'test'; + + expect(sut.transaction, 'test'); + }); + + test('sets transaction overwrites span name', () { + final sut = fixture.getSut(); + + sut.span = fixture.sentryTracer; + sut.transaction = 'test'; + + expect(sut.transaction, 'test'); + expect((sut.span as SentryTracer).name, 'test'); + }); + + test('sets span overwrites transaction name', () { + final sut = fixture.getSut(); + + sut.span = fixture.sentryTracer; + + expect(sut.transaction, 'name'); + expect((sut.span as SentryTracer).name, 'name'); + }); + + test('removing span resets transaction if not set separately', () { + final sut = fixture.getSut(); + + sut.span = fixture.sentryTracer; + sut.span = null; + + expect(sut.transaction, isNull); + }); + + test('removing span does not reset transaction if set separately', () { + final sut = fixture.getSut(); + + sut.transaction = 'test'; + sut.span = fixture.sentryTracer; + sut.span = null; + + expect(sut.transaction, 'test'); + }); + + test('sets $SentryUser', () { + final sut = fixture.getSut(); + + final user = SentryUser(id: 'test'); + sut.setUser(user); + + expect(sut.user, user); + }); + + test('sets fingerprint', () { + final sut = fixture.getSut(); + + final fingerprints = ['test']; + sut.fingerprint = fingerprints; + + expect(sut.fingerprint, fingerprints); + }); + + test('sets replay ID', () { + final sut = fixture.getSut(); + + sut.replayId = SentryId.fromId('1'); + + expect(sut.replayId, SentryId.fromId('1')); + }); + + test('adds $Breadcrumb', () { + final sut = fixture.getSut(); + + final breadcrumb = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + sut.addBreadcrumb(breadcrumb); + + expect(sut.breadcrumbs.last, breadcrumb); + }); + + test('beforeBreadcrumb called with user provided hint', () { + Hint? actual; + BeforeBreadcrumbCallback bb = (_, hint) { + actual = hint; + return null; + }; + final sut = fixture.getSut( + beforeBreadcrumbCallback: bb, + ); + + final breadcrumb = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + final hint = Hint.withMap({'user-name': 'joe dirt'}); + sut.addBreadcrumb(breadcrumb, hint: hint); + + expect(actual?.get('user-name'), 'joe dirt'); + }); + + test('Executes and drops $Breadcrumb', () { + final sut = fixture.getSut( + beforeBreadcrumbCallback: fixture.beforeBreadcrumbCallback, + ); + + final breadcrumb = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + sut.addBreadcrumb(breadcrumb); + + expect(sut.breadcrumbs.length, 0); + }); + + test('Executes and mutates $Breadcrumb', () { + final sut = fixture.getSut( + beforeBreadcrumbCallback: fixture.beforeBreadcrumbMutateCallback, + ); + + final breadcrumb = Breadcrumb( + message: 'message', + timestamp: DateTime.utc(2019), + ); + sut.addBreadcrumb(breadcrumb); + + expect(sut.breadcrumbs.first.message, 'new message'); + }); + + test('adds $EventProcessor', () { + final sut = fixture.getSut(); + + sut.addEventProcessor(fixture.processor); + + expect(sut.eventProcessors.last, isA()); + }); + + test('respects max $Breadcrumb', () { + final maxBreadcrumbs = 2; + final sut = fixture.getSut(maxBreadcrumbs: maxBreadcrumbs); + + final breadcrumb1 = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + final breadcrumb2 = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + final breadcrumb3 = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + sut.addBreadcrumb(breadcrumb1); + sut.addBreadcrumb(breadcrumb2); + sut.addBreadcrumb(breadcrumb3); + + expect(sut.breadcrumbs.length, maxBreadcrumbs); + }); + + test('rotates $Breadcrumb', () { + final sut = fixture.getSut(maxBreadcrumbs: 2); + + final breadcrumb1 = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + final breadcrumb2 = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + final breadcrumb3 = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + sut.addBreadcrumb(breadcrumb1); + sut.addBreadcrumb(breadcrumb2); + sut.addBreadcrumb(breadcrumb3); + + expect(sut.breadcrumbs.first, breadcrumb2); + + expect(sut.breadcrumbs.last, breadcrumb3); + }); + + test('empty $Breadcrumb list', () { + final maxBreadcrumbs = 0; + final sut = fixture.getSut(maxBreadcrumbs: maxBreadcrumbs); + + final breadcrumb1 = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + sut.addBreadcrumb(breadcrumb1); + + expect(sut.breadcrumbs.length, maxBreadcrumbs); + }); + + test('clears $Breadcrumb list', () { + final sut = fixture.getSut(); + + final breadcrumb1 = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + sut.addBreadcrumb(breadcrumb1); + sut.clear(); + + expect(sut.breadcrumbs.length, 0); + }); + + test('adds $SentryAttachment', () { + final sut = fixture.getSut(); + + final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); + sut.addAttachment(attachment); + + expect(sut.attachments.last, attachment); + expect(sut.attachments.length, 1); + }); + + test('clear() removes all $SentryAttachment', () { + final sut = fixture.getSut(); + + final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); + sut.addAttachment(attachment); + expect(sut.attachments.length, 1); + sut.clear(); + + expect(sut.attachments.length, 0); + }); + + test('clearAttachments() removes all $SentryAttachment', () { + final sut = fixture.getSut(); + + final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); + sut.addAttachment(attachment); + expect(sut.attachments.length, 1); + sut.clearAttachments(); + + expect(sut.attachments.length, 0); + }); + + test('sets tag', () { + final sut = fixture.getSut(); + + sut.setTag('test', 'test'); + + expect(sut.tags['test'], 'test'); + }); + + test('removes tag', () { + final sut = fixture.getSut(); + + sut.setTag('test', 'test'); + sut.removeTag('test'); + + expect(sut.tags['test'], null); + }); + + test('sets extra', () { + final sut = fixture.getSut(); + + sut.setExtra('test', 'test'); + + expect(sut.extra['test'], 'test'); + }); + + test('removes extra', () { + final sut = fixture.getSut(); + + sut.setExtra('test', 'test'); + sut.removeExtra('test'); + + expect(sut.extra['test'], null); + }); + + test('clears $Scope', () { + final sut = fixture.getSut(); + + final breadcrumb1 = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + sut.addBreadcrumb(breadcrumb1); + + sut.level = SentryLevel.debug; + sut.transaction = 'test'; + sut.span = null; + sut.replayId = SentryId.newId(); + + final user = SentryUser(id: 'test'); + sut.setUser(user); + + final fingerprints = ['test']; + sut.fingerprint = fingerprints; + + sut.setTag('test', 'test'); + sut.setExtra('test', 'test'); + + sut.addEventProcessor(fixture.processor); + + sut.clear(); + + expect(sut.breadcrumbs.length, 0); + expect(sut.level, null); + expect(sut.transaction, null); + expect(sut.span, null); + expect(sut.user, null); + expect(sut.fingerprint.length, 0); + expect(sut.tags.length, 0); + expect(sut.extra.length, 0); + expect(sut.eventProcessors.length, 0); + expect(sut.replayId, isNull); + }); + + test('clones', () async { + final sut = fixture.getSut(); + + await sut.addBreadcrumb(Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + )); + sut.addAttachment(SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt')); + sut.span = NoOpSentrySpan(); + sut.level = SentryLevel.warning; + sut.replayId = SentryId.newId(); + await sut.setUser(SentryUser(id: 'id')); + await sut.setTag('key', 'vakye'); + await sut.setExtra('key', 'vakye'); + sut.transaction = 'transaction'; + + final clone = sut.clone(); + expect(sut.user, clone.user); + expect(sut.transaction, clone.transaction); + expect(sut.extra, clone.extra); + expect(sut.tags, clone.tags); + expect(sut.breadcrumbs, clone.breadcrumbs); + expect(sut.contexts, clone.contexts); + expect(sut.attachments, clone.attachments); + expect(sut.level, clone.level); + expect(ListEquality().equals(sut.fingerprint, clone.fingerprint), true); + expect( + ListEquality().equals(sut.eventProcessors, clone.eventProcessors), + true, + ); + expect(sut.span, clone.span); + expect(sut.replayId, clone.replayId); + }); + + test('clone does not additionally call observers', () async { + final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); + + await sut.setContexts("fixture-contexts-key", "fixture-contexts-value"); + await sut.removeContexts("fixture-contexts-key"); + await sut.setUser(SentryUser(username: "fixture-username")); + await sut.addBreadcrumb(Breadcrumb()); + await sut.clearBreadcrumbs(); + await sut.setExtra("fixture-extra-key", "fixture-extra-value"); + await sut.removeExtra("fixture-extra-key"); + await sut.setTag("fixture-tag-key", "fixture-tag-value"); + await sut.removeTag("fixture-tag-key"); + + sut.clone(); + + expect(1, fixture.mockScopeObserver.numberOfSetContextsCalls); + expect(1, fixture.mockScopeObserver.numberOfRemoveContextsCalls); + expect(1, fixture.mockScopeObserver.numberOfSetUserCalls); + expect(1, fixture.mockScopeObserver.numberOfAddBreadcrumbCalls); + expect(1, fixture.mockScopeObserver.numberOfClearBreadcrumbsCalls); + expect(1, fixture.mockScopeObserver.numberOfSetExtraCalls); + expect(1, fixture.mockScopeObserver.numberOfRemoveExtraCalls); + expect(1, fixture.mockScopeObserver.numberOfSetTagCalls); + expect(1, fixture.mockScopeObserver.numberOfRemoveTagCalls); + }); + + test('clone has disabled scope sync', () async { + final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); + final clone = sut.clone(); + + await clone.setContexts("fixture-contexts-key", "fixture-contexts-value"); + expect(0, fixture.mockScopeObserver.numberOfSetContextsCalls); + }); + + test('clone shares propagation context to maintain trace continuity', () { + final sut = fixture.getSut(); + + // Clone the scope + final clone = sut.clone(); + + // Verify the propagation context is shared (same instance) + expect(identical(sut.propagationContext, clone.propagationContext), true, + reason: 'Propagation context should be the same instance'); + expect(clone.propagationContext.traceId, sut.propagationContext.traceId); + }); + + group('Scope apply', () { + final scopeUser = SentryUser( + id: '800', + username: 'first-user', + email: 'first@user.lan', + ipAddress: '127.0.0.1', + data: const {'first-sign-in': '2020-01-01'}, + ); + + final breadcrumb = Breadcrumb(message: 'Authenticated'); + + test('apply context to event', () async { + final event = SentryEvent( + tags: const {'etag': '987'}, + extra: const {'e-infos': 'abc'}, + ); + final scope = Scope(defaultTestOptions()) + ..fingerprint = ['example-dart'] + ..transaction = '/example/app' + ..level = SentryLevel.warning + ..addEventProcessor(AddTagsEventProcessor({'page-locale': 'en-us'})); + + await scope.addBreadcrumb(breadcrumb); + await scope.setTag('build', '579'); + await scope.setExtra('company-name', 'Dart Inc'); + await scope.setContexts('theme', 'material'); + await scope.setContexts( + SentryFeatureFlags.type, + SentryFeatureFlags( + values: [SentryFeatureFlag(flag: 'foo', result: true)], + )); + await scope.setUser(scopeUser); + + final updatedEvent = await scope.applyToEvent(event, Hint()); + + expect(updatedEvent?.user, scopeUser); + expect(updatedEvent?.transaction, '/example/app'); + expect(updatedEvent?.fingerprint, ['example-dart']); + expect(updatedEvent?.breadcrumbs, [breadcrumb]); + expect(updatedEvent?.level, SentryLevel.warning); + expect(updatedEvent?.tags, + {'etag': '987', 'build': '579', 'page-locale': 'en-us'}); + expect( + updatedEvent?.extra, {'e-infos': 'abc', 'company-name': 'Dart Inc'}); + expect(updatedEvent?.contexts['theme'], {'value': 'material'}); + expect(updatedEvent?.contexts[SentryFeatureFlags.type]?.values.first.flag, + 'foo'); + expect( + updatedEvent?.contexts[SentryFeatureFlags.type]?.values.first.result, + true); + }); + + test('apply trace context to event with active span', () async { + final event = SentryEvent(); + final scope = Scope(defaultTestOptions())..span = fixture.sentryTracer; + + final updatedEvent = await scope.applyToEvent(event, Hint()); + + expect(updatedEvent?.contexts['trace'] is SentryTraceContext, isTrue); + }); + + test('apply trace context to event with propagation context', () async { + final event = SentryEvent(); + final event2 = SentryEvent(); + final scope = Scope(defaultTestOptions()); + + final updatedEvent = await scope.applyToEvent(event, Hint()); + + final traceContext = + updatedEvent?.contexts['trace'] as SentryTraceContext; + final spanId1 = traceContext.spanId; + expect(traceContext.traceId, scope.propagationContext.traceId); + + final updatedEvent2 = await scope.applyToEvent(event2, Hint()); + final traceContext2 = + updatedEvent2?.contexts['trace'] as SentryTraceContext; + final spanId2 = traceContext2.spanId; + + // trace contexts from the scope should always re-generate span ids + expect(spanId1, isNot(spanId2)); + }); + + test('should not apply the scope properties when event already has it ', + () async { + final eventUser = SentryUser(id: '123'); + final eventBreadcrumb = Breadcrumb(message: 'event-breadcrumb'); + + final event = SentryEvent( + transaction: '/event/transaction', + user: eventUser, + fingerprint: ['event-fingerprint'], + breadcrumbs: [eventBreadcrumb], + ); + final scope = Scope(defaultTestOptions()) + ..fingerprint = ['example-dart'] + ..transaction = '/example/app'; + + await scope.addBreadcrumb(breadcrumb); + await scope.setUser(scopeUser); + + final updatedEvent = await scope.applyToEvent(event, Hint()); + + expect(updatedEvent?.user, isNotNull); + expect(updatedEvent?.user?.id, eventUser.id); + expect(updatedEvent?.transaction, '/event/transaction'); + expect(updatedEvent?.fingerprint, ['event-fingerprint']); + expect(updatedEvent?.breadcrumbs, [eventBreadcrumb]); + }); + + test( + 'should not apply the scope.contexts values if the event already has it', + () async { + final event = SentryEvent( + contexts: Contexts( + device: SentryDevice(name: 'event-device'), + app: SentryApp(name: 'event-app'), + gpu: SentryGpu(name: 'event-gpu'), + runtimes: [SentryRuntime(name: 'event-runtime')], + browser: SentryBrowser(name: 'event-browser'), + operatingSystem: SentryOperatingSystem(name: 'event-os'), + ), + ); + final scope = Scope(defaultTestOptions()); + await scope.setContexts( + SentryDevice.type, + SentryDevice(name: 'context-device'), + ); + await scope.setContexts( + SentryApp.type, + SentryApp(name: 'context-app'), + ); + await scope.setContexts( + SentryGpu.type, + SentryGpu(name: 'context-gpu'), + ); + await scope.setContexts( + SentryRuntime.listType, + [SentryRuntime(name: 'context-runtime')], + ); + await scope.setContexts( + SentryBrowser.type, + SentryBrowser(name: 'context-browser'), + ); + await scope.setContexts( + SentryOperatingSystem.type, + SentryOperatingSystem(name: 'context-os'), + ); + + final updatedEvent = await scope.applyToEvent(event, Hint()); + + expect(updatedEvent?.contexts[SentryDevice.type].name, 'event-device'); + expect(updatedEvent?.contexts[SentryApp.type].name, 'event-app'); + expect(updatedEvent?.contexts[SentryGpu.type].name, 'event-gpu'); + expect(updatedEvent?.contexts[SentryRuntime.listType].first.name, + 'event-runtime'); + expect(updatedEvent?.contexts[SentryBrowser.type].name, 'event-browser'); + expect( + updatedEvent?.contexts[SentryOperatingSystem.type].name, 'event-os'); + }); + + test('should apply the scope.contexts values', () async { + final event = SentryEvent(); + final scope = Scope(defaultTestOptions()); + await scope.setContexts( + SentryDevice.type, SentryDevice(name: 'context-device')); + await scope.setContexts(SentryApp.type, SentryApp(name: 'context-app')); + await scope.setContexts(SentryGpu.type, SentryGpu(name: 'context-gpu')); + await scope.setContexts( + SentryRuntime.listType, [SentryRuntime(name: 'context-runtime')]); + await scope.setContexts( + SentryBrowser.type, SentryBrowser(name: 'context-browser')); + await scope.setContexts(SentryOperatingSystem.type, + SentryOperatingSystem(name: 'context-os')); + await scope.setContexts('theme', 'material'); + await scope.setContexts('version', 9); + await scope.setContexts('location', {'city': 'London'}); + await scope.setContexts('items', [1, 2, 3]); + + final updatedEvent = await scope.applyToEvent(event, Hint()); + + expect(updatedEvent?.contexts[SentryDevice.type].name, 'context-device'); + expect(updatedEvent?.contexts[SentryApp.type].name, 'context-app'); + expect(updatedEvent?.contexts[SentryGpu.type].name, 'context-gpu'); + expect( + updatedEvent?.contexts[SentryRuntime.listType].first.name, + 'context-runtime', + ); + expect( + updatedEvent?.contexts[SentryBrowser.type].name, 'context-browser'); + expect(updatedEvent?.contexts[SentryOperatingSystem.type].name, + 'context-os'); + expect(updatedEvent?.contexts['theme']['value'], 'material'); + expect(updatedEvent?.contexts['version']['value'], 9); + expect(updatedEvent?.contexts['location'], {'city': 'London'}); + final items = updatedEvent?.contexts['items']; + expect(items['value'], [1, 2, 3]); + }); + + test('should apply the scope level', () async { + final event = SentryEvent(level: SentryLevel.warning); + final scope = Scope(defaultTestOptions())..level = SentryLevel.error; + + final updatedEvent = await scope.applyToEvent(event, Hint()); + + expect(updatedEvent?.level, SentryLevel.error); + }); + + test('should apply the scope transaction from the span', () async { + final event = SentryEvent(); + final scope = Scope(defaultTestOptions())..span = fixture.sentryTracer; + + final updatedEvent = await scope.applyToEvent(event, Hint()); + + expect(updatedEvent?.transaction, 'name'); + }); + + test('should not apply breadcrumbs if feedback event', () async { + final feedback = SentryFeedback( + message: 'fixture-message', + ); + final feedbackEvent = SentryEvent( + type: 'feedback', + contexts: Contexts(feedback: feedback), + level: SentryLevel.info, + ); + final scope = Scope(defaultTestOptions()); + await scope.addBreadcrumb(Breadcrumb(message: 'fixture-breadcrumb')); + + final updatedEvent = await scope.applyToEvent(feedbackEvent, Hint()); + + expect(updatedEvent?.breadcrumbs, isNull); + }); + + test('should not apply extras if feedback event', () async { + final feedback = SentryFeedback( + message: 'fixture-message', + ); + final feedbackEvent = SentryEvent( + type: 'feedback', + contexts: Contexts(feedback: feedback), + level: SentryLevel.info, + ); + final scope = Scope(defaultTestOptions()); + await scope.setExtra('fixture-extra-key', 'fixture-extra-value'); + + final updatedEvent = await scope.applyToEvent(feedbackEvent, Hint()); + + expect(updatedEvent?.extra, isNull); + }); + }); + + test('event processor drops the event', () async { + final sut = fixture.getSut(); + + sut.addEventProcessor(fixture.processor); + + final event = SentryEvent(); + var newEvent = await sut.applyToEvent(event, Hint()); + + expect(newEvent, isNull); + }); + + test('should not apply fingerprint if transaction', () async { + var tr = SentryTransaction(fixture.sentryTracer); + final scope = Scope(defaultTestOptions())..fingerprint = ['test']; + + final updatedTr = await scope.applyToEvent(tr, Hint()); + + expect(updatedTr?.fingerprint, isNull); + }); + + test('should not apply level if transaction', () async { + var tr = SentryTransaction(fixture.sentryTracer); + final scope = Scope(defaultTestOptions())..level = SentryLevel.error; + + final updatedTr = await scope.applyToEvent(tr, Hint()); + + expect(updatedTr?.level, isNull); + }); + + test('apply sampled to trace', () async { + var tr = SentryTransaction(fixture.sentryTracer); + final scope = Scope(defaultTestOptions())..level = SentryLevel.error; + + final updatedTr = await scope.applyToEvent(tr, Hint()); + + expect(updatedTr?.contexts.trace?.sampled, isTrue); + }); + + test('addBreadcrumb should call scope observers', () async { + final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); + await sut.addBreadcrumb(Breadcrumb()); + + expect(true, fixture.mockScopeObserver.calledAddBreadcrumb); + }); + + test('addBreadcrumb passes processed breadcrumb to scope observers', + () async { + final sut = fixture.getSut( + scopeObserver: fixture.mockScopeObserver, + beforeBreadcrumbCallback: ( + Breadcrumb? breadcrumb, + Hint hint, + ) { + breadcrumb?.message = "modified"; + return breadcrumb; + }, + ); + await sut.addBreadcrumb(Breadcrumb()); + + expect(fixture.mockScopeObserver.addedBreadcrumbs[0].message, "modified"); + }); + + test('clearBreadcrumbs should call scope observers', () async { + final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); + await sut.clearBreadcrumbs(); + + expect(true, fixture.mockScopeObserver.calledClearBreadcrumbs); + }); + + test('removeContexts should call scope observers', () async { + final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); + await sut.removeContexts('fixture-key'); + + expect(true, fixture.mockScopeObserver.calledRemoveContexts); + }); + + test('removeExtra should call scope observers', () async { + final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); + await sut.removeExtra('fixture-key'); + + expect(true, fixture.mockScopeObserver.calledRemoveExtra); + }); + + test('removeTag should call scope observers', () async { + final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); + await sut.removeTag('fixture-key'); + + expect(true, fixture.mockScopeObserver.calledRemoveTag); + }); + + test('setContexts should call scope observers', () async { + final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); + await sut.setContexts('fixture-key', 'fixture-value'); + + expect(true, fixture.mockScopeObserver.calledSetContexts); + }); + + test('setExtra should call scope observers', () async { + final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); + await sut.setExtra('fixture-key', 'fixture-value'); + + expect(true, fixture.mockScopeObserver.calledSetExtra); + }); + + test('setTag should call scope observers', () async { + final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); + await sut.setTag('fixture-key', 'fixture-value'); + + expect(true, fixture.mockScopeObserver.calledSetTag); + }); + + test('setUser should call scope observers', () async { + final sut = fixture.getSut(scopeObserver: fixture.mockScopeObserver); + await sut.setUser(null); + + expect(true, fixture.mockScopeObserver.calledSetUser); + }); + + group("Scope exceptions", () { + test("addBreadcrumb with beforeBreadcrumb error handled ", () async { + final exception = Exception("before breadcrumb exception"); + + fixture.options.automatedTestMode = false; + final sut = fixture.getSut( + beforeBreadcrumbCallback: ( + Breadcrumb? breadcrumb, + Hint hint, + ) { + throw exception; + }, + debug: true); + + final breadcrumb = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + + await sut.addBreadcrumb(breadcrumb); + + expect(fixture.loggedException, exception); + expect(fixture.loggedLevel, SentryLevel.error); + }); + + test("clone with beforeBreadcrumb error handled ", () async { + var numberOfBeforeBreadcrumbCalls = 0; + final exception = Exception("before breadcrumb exception"); + + fixture.options.automatedTestMode = false; + final sut = fixture.getSut( + beforeBreadcrumbCallback: ( + Breadcrumb? breadcrumb, + Hint hint, + ) { + if (numberOfBeforeBreadcrumbCalls > 0) { + throw exception; + } + numberOfBeforeBreadcrumbCalls += 1; + return breadcrumb; + }, + debug: true); + + final breadcrumb = Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + ); + await sut.addBreadcrumb(breadcrumb); + sut.clone(); + + expect(fixture.loggedException, exception); + expect(fixture.loggedLevel, SentryLevel.error); + }); + }); + + // addBreadcrumb + // clone +} + +class Fixture { + final mockScopeObserver = MockScopeObserver(); + + final options = defaultTestOptions(); + + final sentryTracer = SentryTracer( + SentryTransactionContext( + 'name', + 'op', + samplingDecision: SentryTracesSamplingDecision(true), + ), + MockHub(), + ); + + SentryLevel? loggedLevel; + Object? loggedException; + + Scope getSut({ + int maxBreadcrumbs = 100, + BeforeBreadcrumbCallback? beforeBreadcrumbCallback, + ScopeObserver? scopeObserver, + bool debug = false, + }) { + options.maxBreadcrumbs = maxBreadcrumbs; + options.beforeBreadcrumb = beforeBreadcrumbCallback; + options.debug = debug; + options.log = mockLogger; + + if (scopeObserver != null) { + options.addScopeObserver(scopeObserver); + } + return Scope(options); + } + + EventProcessor get processor => DropAllEventProcessor(); + + Breadcrumb? beforeBreadcrumbCallback(Breadcrumb? breadcrumb, Hint hint) => + null; + + Breadcrumb? beforeBreadcrumbMutateCallback( + Breadcrumb? breadcrumb, Hint hint) { + breadcrumb?.message = 'new message'; + return breadcrumb; + } + + void mockLogger( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + loggedLevel = level; + loggedException = exception; + } +} + +class AddTagsEventProcessor implements EventProcessor { + final Map tags; + + AddTagsEventProcessor(this.tags); + + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + return event..tags?.addAll(tags); + } +} diff --git a/packages/dart/test/sdk_lifecycle_hooks_test.dart b/packages/dart/test/sdk_lifecycle_hooks_test.dart new file mode 100644 index 0000000000..4ba5571afa --- /dev/null +++ b/packages/dart/test/sdk_lifecycle_hooks_test.dart @@ -0,0 +1,111 @@ +// This tests that the base functionality of the hooks function correctly. + +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import 'test_utils.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + group('SdkLifecycleRegistry', () { + test('registers callback for given event type', () { + final registry = fixture.getSut(); + final cb = (OnBeforeSendEvent _) {}; + + registry.registerCallback(cb); + + expect(registry.lifecycleCallbacks[OnBeforeSendEvent], contains(cb)); + }); + + test('removes previously registered callback', () { + final registry = fixture.getSut(); + final cb = (OnBeforeSendEvent _) {}; + + registry.registerCallback(cb); + registry.removeCallback(cb); + + final callbacks = registry.lifecycleCallbacks[OnBeforeSendEvent]; + expect(callbacks, isNotNull); + expect(callbacks, isEmpty); + }); + + test('dispatch executes registered synchronous callback', () async { + final registry = fixture.getSut(); + var executed = false; + final cb = (OnBeforeSendEvent _) { + executed = true; + }; + + registry.registerCallback(cb); + + await registry.dispatchCallback( + OnBeforeSendEvent(SentryEvent(), Hint()), + ); + + expect(executed, isTrue); + }); + + test('dispatch executes registered asynchronous callback', () async { + final registry = fixture.getSut(); + var executed = false; + final cb = (OnBeforeSendEvent _) async { + await Future.delayed(Duration.zero); + executed = true; + }; + + registry.registerCallback(cb); + + await registry.dispatchCallback( + OnBeforeSendEvent(SentryEvent(), Hint()), + ); + + expect(executed, isTrue); + }); + + test('dispatch does not execute callback after removal', () async { + final registry = fixture.getSut(); + var executed = false; + final cb = (OnBeforeSendEvent _) { + executed = true; + }; + + registry.registerCallback(cb); + registry.removeCallback(cb); + + await registry.dispatchCallback( + OnBeforeSendEvent(SentryEvent(), Hint()), + ); + + expect(executed, isFalse); + }); + + test('dispatch handles exceptions thrown in the callback', () async { + fixture.options.automatedTestMode = false; + final registry = fixture.getSut(); + final cb = (OnBeforeSendEvent _) { + throw StateError('failure in callback'); + }; + + registry.registerCallback(cb); + + expect( + () async => registry.dispatchCallback( + OnBeforeSendEvent(SentryEvent(), Hint()), + ), + returnsNormally); + }); + }); +} + +class Fixture { + final SentryOptions options = defaultTestOptions(); + + SdkLifecycleRegistry getSut() { + return SdkLifecycleRegistry(options); + } +} diff --git a/packages/dart/test/sentry_attachment_io_test.dart b/packages/dart/test/sentry_attachment_io_test.dart new file mode 100644 index 0000000000..1dd1850f41 --- /dev/null +++ b/packages/dart/test/sentry_attachment_io_test.dart @@ -0,0 +1,32 @@ +@TestOn('vm') +library; + +import 'dart:io'; + +import 'package:sentry/sentry_io.dart'; +import 'package:test/test.dart'; + +void main() { + group('$SentryAttachment ctor', () { + test('fromFile', () async { + final file = File('test_resources/testfile.txt'); + + final attachment = IoSentryAttachment.fromFile(file); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'testfile.txt'); + await expectLater( + await attachment.bytes, [102, 111, 111, 32, 98, 97, 114]); + }); + + test('fromPath', () async { + final attachment = + IoSentryAttachment.fromPath('test_resources/testfile.txt'); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'testfile.txt'); + await expectLater( + await attachment.bytes, [102, 111, 111, 32, 98, 97, 114]); + }); + }); +} diff --git a/packages/dart/test/sentry_attachment_test.dart b/packages/dart/test/sentry_attachment_test.dart new file mode 100644 index 0000000000..060b8578f0 --- /dev/null +++ b/packages/dart/test/sentry_attachment_test.dart @@ -0,0 +1,192 @@ +import 'dart:typed_data'; + +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import 'mocks/mock_transport.dart'; +import 'test_utils.dart'; + +void main() { + group('$SentryAttachment ctor', () { + test('default', () async { + final attachment = SentryAttachment.fromLoader( + loader: () => Uint8List.fromList([0, 0, 0, 0]), + filename: 'test.txt', + ); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'test.txt'); + await expectLater(await attachment.bytes, [0, 0, 0, 0]); + }); + + test('fromIntList', () async { + final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'test.txt'); + await expectLater(await attachment.bytes, [0, 0, 0, 0]); + }); + + test('fromUint8List', () async { + final attachment = SentryAttachment.fromUint8List( + Uint8List.fromList([0, 0, 0, 0]), + 'test.txt', + ); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'test.txt'); + await expectLater(await attachment.bytes, [0, 0, 0, 0]); + }); + + test('fromByteData', () async { + final attachment = SentryAttachment.fromByteData( + ByteData.sublistView(Uint8List.fromList([0, 0, 0, 0])), + 'test.txt', + ); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'test.txt'); + await expectLater(await attachment.bytes, [0, 0, 0, 0]); + }); + }); + + group('$Scope $SentryAttachment tests', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('Sending with attachments', () async { + final sut = fixture.getSut(); + await sut.captureEvent(SentryEvent(), withScope: (scope) { + scope.addAttachment( + SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'), + ); + }); + expect(fixture.transport.envelopes.length, 1); + expect(fixture.transport.envelopes.first.items.length, 2); + final attachmentEnvelope = fixture.transport.envelopes.first.items[1]; + expect( + attachmentEnvelope.header.attachmentType, + SentryAttachment.typeAttachmentDefault, + ); + expect( + attachmentEnvelope.header.contentType, + isNull, + ); + expect( + attachmentEnvelope.header.fileName, + 'test.txt', + ); + await expectLater( + (await attachmentEnvelope.dataFactory()).length, + 4, + ); + }); + }); + + group('addToTransactions', () { + test('defaults to false fromLoader', () async { + final attachment = SentryAttachment.fromLoader( + loader: () => Uint8List.fromList([0, 0, 0, 0]), + filename: 'test.txt', + ); + + expect(attachment.addToTransactions, false); + }); + + test('defaults to false fromIntList', () async { + final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); + + expect(attachment.addToTransactions, false); + }); + + test('defaults to false fromUint8List', () async { + final attachment = SentryAttachment.fromUint8List( + Uint8List.fromList([0, 0, 0, 0]), + 'test.txt', + ); + + expect(attachment.addToTransactions, false); + }); + + test('defaults to false fromByteData', () async { + final attachment = SentryAttachment.fromByteData( + ByteData.sublistView(Uint8List.fromList([0, 0, 0, 0])), + 'test.txt', + ); + + expect(attachment.addToTransactions, false); + }); + + test('set fromLoader', () async { + final attachment = SentryAttachment.fromLoader( + loader: () => Uint8List.fromList([0, 0, 0, 0]), + filename: 'test.txt', + addToTransactions: true, + ); + + expect(attachment.addToTransactions, true); + }); + + test('defaults to false fromIntList', () async { + final attachment = SentryAttachment.fromIntList( + [0, 0, 0, 0], + 'test.txt', + addToTransactions: true, + ); + + expect(attachment.addToTransactions, true); + }); + + test('defaults to false fromUint8List', () async { + final attachment = SentryAttachment.fromUint8List( + Uint8List.fromList([0, 0, 0, 0]), + 'test.txt', + addToTransactions: true, + ); + + expect(attachment.addToTransactions, true); + }); + + test('defaults to false fromByteData', () async { + final attachment = SentryAttachment.fromByteData( + ByteData.sublistView(Uint8List.fromList([0, 0, 0, 0])), + 'test.txt', + addToTransactions: true, + ); + + expect(attachment.addToTransactions, true); + }); + + test('fromScreenshotData', () async { + final attachment = + SentryAttachment.fromScreenshotData(Uint8List.fromList([0, 0, 0, 0])); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); + expect(attachment.contentType, 'image/png'); + expect(attachment.filename, 'screenshot.png'); + expect(attachment.addToTransactions, false); + }); + + test('fromViewHierarchy', () async { + final view = SentryViewHierarchy('flutter'); + final attachment = SentryAttachment.fromViewHierarchy(view); + + expect(attachment.attachmentType, SentryAttachment.typeViewHierarchy); + expect(attachment.contentType, 'application/json'); + expect(attachment.filename, 'view-hierarchy.json'); + expect(attachment.addToTransactions, false); + }); + }); +} + +class Fixture { + MockTransport transport = MockTransport(); + + Hub getSut() { + final options = defaultTestOptions(); + options.transport = transport; + return Hub(options); + } +} diff --git a/packages/dart/test/sentry_baggage_test.dart b/packages/dart/test/sentry_baggage_test.dart new file mode 100644 index 0000000000..dccd656bd7 --- /dev/null +++ b/packages/dart/test/sentry_baggage_test.dart @@ -0,0 +1,97 @@ +import 'package:sentry/src/sentry_baggage.dart'; +import 'package:test/test.dart'; + +void main() { + group('$SentryBaggage', () { + test('reads from header string with spaces', () { + final headers = + 'userId = alice , serverNode = DF%2028,isProduction=false'; + final baggage = SentryBaggage.fromHeader(headers); + + expect(baggage.get('userId'), 'alice'); + expect(baggage.get('serverNode'), 'DF 28'); + expect(baggage.get('isProduction'), 'false'); + + expect(baggage.toHeaderString(), + 'userId=alice,serverNode=DF%2028,isProduction=false'); + }); + + test('decodes and encodes the headers', () { + final headers = + 'user%2Bid=alice,server%2Bnode=DF%2028,is%2Bproduction=false'; + final baggage = SentryBaggage.fromHeader(headers); + + expect(baggage.get('user+id'), 'alice'); + expect(baggage.get('server+node'), 'DF 28'); + expect(baggage.get('is+production'), 'false'); + + expect(baggage.toHeaderString(), + 'user%2Bid=alice,server%2Bnode=DF%2028,is%2Bproduction=false'); + }); + + test('reads from header list', () { + final headers = [ + 'userId = alice', + 'serverNode = DF%2028, isProduction = false' + ]; + final baggage = SentryBaggage.fromHeaderList(headers); + + expect(baggage.get('userId'), 'alice'); + expect(baggage.get('serverNode'), 'DF 28'); + expect(baggage.get('isProduction'), 'false'); + + expect(baggage.toHeaderString(), + 'userId=alice,serverNode=DF%2028,isProduction=false'); + }); + + test('reads from empty string', () { + final baggage = SentryBaggage.fromHeader(''); + + expect(baggage.toHeaderString(), isEmpty); + }); + + test('reads from blank string', () { + final baggage = SentryBaggage.fromHeader(' '); + + expect(baggage.toHeaderString(), isEmpty); + }); + + test('drops large values when above the limit', () { + final buffer = StringBuffer(); + for (int i = 0; i < 1000; i++) { + // 10 chars each loop + buffer.write('largeValue'); + } + + // max is 8192 + expect(buffer.length > 8192, isTrue); + final largeValue = buffer.toString(); + + final baggage = SentryBaggage.fromHeader( + 'smallValue=remains,largeValue=$largeValue,otherValue=kept'); + + expect(baggage.get('smallValue'), 'remains'); + expect(baggage.get('otherValue'), 'kept'); + expect(baggage.get('largeValue'), largeValue); + + expect(baggage.toHeaderString(), 'smallValue=remains,otherValue=kept'); + }); + + test('drops items when above the list member limit', () { + final buffer = StringBuffer(); + final match = StringBuffer(); + for (int i = 1; i <= 65; i++) { + final value = '$i=$i,'; + buffer.write(value); + // max is 64 + if (i <= 64) { + match.write(value); + } + } + final baggage = SentryBaggage.fromHeader(buffer.toString()); + + expect(baggage.toHeaderString(), + match.toString().substring(0, match.length - 1)); + }); + }); +} diff --git a/packages/dart/test/sentry_browser_test.dart b/packages/dart/test/sentry_browser_test.dart new file mode 100644 index 0000000000..cb83f510c9 --- /dev/null +++ b/packages/dart/test/sentry_browser_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +@TestOn('browser') +library; + +import 'package:test/test.dart'; + +import 'test_utils.dart'; + +void main() { + runTest(isWeb: true); +} diff --git a/packages/dart/test/sentry_client_lifecycle_test.dart b/packages/dart/test/sentry_client_lifecycle_test.dart new file mode 100644 index 0000000000..7d780d0cf9 --- /dev/null +++ b/packages/dart/test/sentry_client_lifecycle_test.dart @@ -0,0 +1,195 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/platform/mock_platform.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:test/test.dart'; + +import 'mocks/mock_client_report_recorder.dart'; +import 'mocks/mock_log_batcher.dart'; +import 'mocks/mock_transport.dart'; +import 'sentry_client_test.dart'; +import 'test_utils.dart'; +import 'utils/url_details_test.dart'; + +void main() { + group('SDK lifecycle callbacks', () { + late Fixture fixture; + + setUp(() => fixture = Fixture()); + + group('Logs', () { + SentryLog givenLog() { + return SentryLog( + timestamp: DateTime.now(), + traceId: SentryId.newId(), + level: SentryLogLevel.info, + body: 'test', + attributes: { + 'attribute': SentryLogAttribute.string('value'), + }, + ); + } + + test('captureLog triggers OnBeforeCaptureLog', () async { + fixture.options.enableLogs = true; + fixture.options.environment = 'test-environment'; + fixture.options.release = 'test-release'; + + final log = givenLog(); + + final scope = Scope(fixture.options); + final span = MockSpan(); + scope.span = span; + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + client.lifeCycleRegistry.registerCallback((event) { + event.log.attributes['test'] = + SentryLogAttribute.string('test-value'); + }); + + await client.captureLog(log, scope: scope); + + final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; + expect(mockLogBatcher.addLogCalls.length, 1); + final capturedLog = mockLogBatcher.addLogCalls.first; + + expect(capturedLog.attributes['test']?.value, "test-value"); + expect(capturedLog.attributes['test']?.type, 'string'); + }); + }); + + group('SentryEvent', () { + test('captureEvent triggers OnBeforeSendEvent', () async { + fixture.options.enableLogs = true; + fixture.options.environment = 'test-environment'; + fixture.options.release = 'test-release'; + + final event = SentryEvent(); + + final scope = Scope(fixture.options); + final span = MockSpan(); + scope.span = span; + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + client.lifeCycleRegistry.registerCallback((event) { + event.event.release = '999'; + }); + + await client.captureEvent(event, scope: scope); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.release, '999'); + }); + }); + }); +} + +class Fixture { + final recorder = MockClientReportRecorder(); + final transport = MockTransport(); + + final options = defaultTestOptions() + ..platform = MockPlatform.iOS() + ..groupExceptions = true; + + late SentryTransactionContext _context; + late SentryTracer tracer; + + SentryLevel? loggedLevel; + Object? loggedException; + + SentryClient getSut({ + bool sendDefaultPii = false, + bool attachStacktrace = true, + bool attachThreads = false, + double? sampleRate, + BeforeSendCallback? beforeSend, + BeforeSendTransactionCallback? beforeSendTransaction, + BeforeSendCallback? beforeSendFeedback, + EventProcessor? eventProcessor, + bool provideMockRecorder = true, + bool debug = false, + Transport? transport, + }) { + options.tracesSampleRate = 1.0; + options.sendDefaultPii = sendDefaultPii; + options.attachStacktrace = attachStacktrace; + options.attachThreads = attachThreads; + options.sampleRate = sampleRate; + options.beforeSend = beforeSend; + options.beforeSendTransaction = beforeSendTransaction; + options.beforeSendFeedback = beforeSendFeedback; + options.debug = debug; + options.log = mockLogger; + + if (eventProcessor != null) { + options.addEventProcessor(eventProcessor); + } + + // Internally also creates a SentryClient instance + final hub = Hub(options); + _context = SentryTransactionContext( + 'name', + 'op', + ); + tracer = SentryTracer(_context, hub); + + // Reset transport + options.transport = transport ?? this.transport; + + // Again create SentryClient instance + final client = SentryClient(options); + + if (provideMockRecorder) { + options.recorder = recorder; + } + return client; + } + + Future droppingBeforeSend(SentryEvent event, Hint hint) async { + return null; + } + + SentryTransaction fakeTransaction() { + return SentryTransaction( + tracer, + sdk: SdkVersion(name: 'sdk1', version: '1.0.0'), + breadcrumbs: [], + ); + } + + SentryEvent fakeFeedbackEvent() { + return SentryEvent( + type: 'feedback', + contexts: Contexts(feedback: fakeFeedback()), + level: SentryLevel.info, + ); + } + + SentryFeedback fakeFeedback() { + return SentryFeedback( + message: 'fixture-message', + contactEmail: 'fixture-contactEmail', + name: 'fixture-name', + replayId: 'fixture-replayId', + url: "https://fixture-url.com", + associatedEventId: SentryId.fromId('1d49af08b6e2c437f9052b1ecfd83dca'), + ); + } + + void mockLogger( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + loggedLevel = level; + loggedException = exception; + } +} diff --git a/packages/dart/test/sentry_client_sdk_lifecycle_test.dart b/packages/dart/test/sentry_client_sdk_lifecycle_test.dart new file mode 100644 index 0000000000..7d780d0cf9 --- /dev/null +++ b/packages/dart/test/sentry_client_sdk_lifecycle_test.dart @@ -0,0 +1,195 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/platform/mock_platform.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:test/test.dart'; + +import 'mocks/mock_client_report_recorder.dart'; +import 'mocks/mock_log_batcher.dart'; +import 'mocks/mock_transport.dart'; +import 'sentry_client_test.dart'; +import 'test_utils.dart'; +import 'utils/url_details_test.dart'; + +void main() { + group('SDK lifecycle callbacks', () { + late Fixture fixture; + + setUp(() => fixture = Fixture()); + + group('Logs', () { + SentryLog givenLog() { + return SentryLog( + timestamp: DateTime.now(), + traceId: SentryId.newId(), + level: SentryLogLevel.info, + body: 'test', + attributes: { + 'attribute': SentryLogAttribute.string('value'), + }, + ); + } + + test('captureLog triggers OnBeforeCaptureLog', () async { + fixture.options.enableLogs = true; + fixture.options.environment = 'test-environment'; + fixture.options.release = 'test-release'; + + final log = givenLog(); + + final scope = Scope(fixture.options); + final span = MockSpan(); + scope.span = span; + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + client.lifeCycleRegistry.registerCallback((event) { + event.log.attributes['test'] = + SentryLogAttribute.string('test-value'); + }); + + await client.captureLog(log, scope: scope); + + final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; + expect(mockLogBatcher.addLogCalls.length, 1); + final capturedLog = mockLogBatcher.addLogCalls.first; + + expect(capturedLog.attributes['test']?.value, "test-value"); + expect(capturedLog.attributes['test']?.type, 'string'); + }); + }); + + group('SentryEvent', () { + test('captureEvent triggers OnBeforeSendEvent', () async { + fixture.options.enableLogs = true; + fixture.options.environment = 'test-environment'; + fixture.options.release = 'test-release'; + + final event = SentryEvent(); + + final scope = Scope(fixture.options); + final span = MockSpan(); + scope.span = span; + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + client.lifeCycleRegistry.registerCallback((event) { + event.event.release = '999'; + }); + + await client.captureEvent(event, scope: scope); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.release, '999'); + }); + }); + }); +} + +class Fixture { + final recorder = MockClientReportRecorder(); + final transport = MockTransport(); + + final options = defaultTestOptions() + ..platform = MockPlatform.iOS() + ..groupExceptions = true; + + late SentryTransactionContext _context; + late SentryTracer tracer; + + SentryLevel? loggedLevel; + Object? loggedException; + + SentryClient getSut({ + bool sendDefaultPii = false, + bool attachStacktrace = true, + bool attachThreads = false, + double? sampleRate, + BeforeSendCallback? beforeSend, + BeforeSendTransactionCallback? beforeSendTransaction, + BeforeSendCallback? beforeSendFeedback, + EventProcessor? eventProcessor, + bool provideMockRecorder = true, + bool debug = false, + Transport? transport, + }) { + options.tracesSampleRate = 1.0; + options.sendDefaultPii = sendDefaultPii; + options.attachStacktrace = attachStacktrace; + options.attachThreads = attachThreads; + options.sampleRate = sampleRate; + options.beforeSend = beforeSend; + options.beforeSendTransaction = beforeSendTransaction; + options.beforeSendFeedback = beforeSendFeedback; + options.debug = debug; + options.log = mockLogger; + + if (eventProcessor != null) { + options.addEventProcessor(eventProcessor); + } + + // Internally also creates a SentryClient instance + final hub = Hub(options); + _context = SentryTransactionContext( + 'name', + 'op', + ); + tracer = SentryTracer(_context, hub); + + // Reset transport + options.transport = transport ?? this.transport; + + // Again create SentryClient instance + final client = SentryClient(options); + + if (provideMockRecorder) { + options.recorder = recorder; + } + return client; + } + + Future droppingBeforeSend(SentryEvent event, Hint hint) async { + return null; + } + + SentryTransaction fakeTransaction() { + return SentryTransaction( + tracer, + sdk: SdkVersion(name: 'sdk1', version: '1.0.0'), + breadcrumbs: [], + ); + } + + SentryEvent fakeFeedbackEvent() { + return SentryEvent( + type: 'feedback', + contexts: Contexts(feedback: fakeFeedback()), + level: SentryLevel.info, + ); + } + + SentryFeedback fakeFeedback() { + return SentryFeedback( + message: 'fixture-message', + contactEmail: 'fixture-contactEmail', + name: 'fixture-name', + replayId: 'fixture-replayId', + url: "https://fixture-url.com", + associatedEventId: SentryId.fromId('1d49af08b6e2c437f9052b1ecfd83dca'), + ); + } + + void mockLogger( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + loggedLevel = level; + loggedException = exception; + } +} diff --git a/packages/dart/test/sentry_client_test.dart b/packages/dart/test/sentry_client_test.dart new file mode 100644 index 0000000000..33eb43697c --- /dev/null +++ b/packages/dart/test/sentry_client_test.dart @@ -0,0 +1,2833 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/client_reports/discard_reason.dart'; +import 'package:sentry/src/client_reports/noop_client_report_recorder.dart'; +import 'package:sentry/src/event_processor/exception/exception_group_event_processor.dart'; +import 'package:sentry/src/platform/mock_platform.dart'; +import 'package:sentry/src/sentry_item_type.dart'; +import 'package:sentry/src/sentry_stack_trace_factory.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:sentry/src/transport/client_report_transport.dart'; +import 'package:sentry/src/transport/data_category.dart'; +import 'package:sentry/src/transport/noop_transport.dart'; +import 'package:sentry/src/transport/spotlight_http_transport.dart'; +import 'package:sentry/src/utils/iterable_utils.dart'; +import 'package:test/test.dart'; +import 'package:sentry/src/noop_log_batcher.dart'; + +import 'mocks.dart'; +import 'mocks/mock_client_report_recorder.dart'; +import 'mocks/mock_hub.dart'; +import 'mocks/mock_transport.dart'; +import 'test_utils.dart'; +import 'utils/url_details_test.dart'; +import 'mocks/mock_log_batcher.dart'; + +void main() { + group('SentryClient captures message', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('should capture event stacktrace', () async { + final client = fixture.getSut(attachStacktrace: false); + final event = SentryEvent(); + await client.captureEvent( + event, + stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', + ); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.threads?.first.stacktrace, isA()); + }); + + test('should attach event stacktrace', () async { + final client = fixture.getSut(); + final event = SentryEvent(); + await client.captureEvent(event); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.threads?.first.stacktrace, isA()); + }); + + test('should not attach event stacktrace', () async { + final client = fixture.getSut(attachStacktrace: false); + final event = SentryEvent(); + await client.captureEvent(event); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.threads?.first.stacktrace, isNull); + }); + + test('should not attach event stacktrace if event has throwable', () async { + final client = fixture.getSut(); + + SentryEvent event; + try { + throw StateError('Error'); + } on Error catch (err) { + event = SentryEvent(throwable: err); + } + + await client.captureEvent( + event, + stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', + ); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.threads?.first.stacktrace, isNull); + expect(capturedEvent.exceptions?.first.stackTrace, isNotNull); + }); + + test('should not attach event stacktrace if event has exception', () async { + final client = fixture.getSut(); + + final exception = SentryException( + type: 'Exception', + value: 'an exception', + stackTrace: SentryStackTraceFactory(fixture.options) + .parse('#0 baz (file:///pathto/test.dart:50:3)'), + ); + final event = SentryEvent(exceptions: [exception]); + + await client.captureEvent( + event, + stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', + ); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.threads?.first.stacktrace, isNull); + expect(capturedEvent.exceptions?.first.stackTrace, isNotNull); + }); + + test( + 'should attach isolate info in thread', + () async { + final client = fixture.getSut(attachThreads: true); + + await client.captureException( + Exception(), + stackTrace: StackTrace.current, + ); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.threads?.first.current, true); + expect(capturedEvent.threads?.first.crashed, true); + expect(capturedEvent.threads?.first.name, isNotNull); + expect(capturedEvent.threads?.first.id, isNotNull); + + expect( + capturedEvent.exceptions?.first.threadId, + capturedEvent.threads?.first.id, + ); + }, + onPlatform: { + 'js': Skip("Isolates don't exist on the web"), + 'wasm': Skip("Isolates don't exist on the web") + }, + ); + + test( + 'should not attach isolate info in thread if disabled', + () async { + final client = fixture.getSut(attachThreads: false); + + await client.captureException( + Exception(), + stackTrace: StackTrace.current, + ); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.threads, null); + }, + onPlatform: {'js': Skip("Isolates don't exist on the web")}, + ); + + test('should capture message', () async { + final client = fixture.getSut(); + await client.captureMessage( + 'simple message 1', + template: 'simple message %d', + params: [1], + level: SentryLevel.error, + ); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.message!.formatted, 'simple message 1'); + expect(capturedEvent.message!.template, 'simple message %d'); + expect(capturedEvent.message!.params, [1]); + expect(capturedEvent.level, SentryLevel.error); + }); + + test('capture message defaults to info level', () async { + final client = fixture.getSut(); + await client.captureMessage( + 'simple message 1', + ); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.level, SentryLevel.info); + }); + + test('should capture message without stacktrace', () async { + final client = fixture.getSut(attachStacktrace: false); + await client.captureMessage('message', level: SentryLevel.error); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.threads?.first.stacktrace, isNull); + }); + + test('event envelope contains dsn', () async { + final client = fixture.getSut(); + final event = SentryEvent(); + await client.captureEvent(event); + + final capturedEnvelope = (fixture.transport).envelopes.first; + + expect(capturedEnvelope.header.dsn, fixture.options.dsn); + }); + }); + + group('SentryClient captures exception', () { + Error error; + StackTrace stackTrace; + + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('should capture error', () async { + try { + throw StateError('Error'); + } on Error catch (err, stack) { + error = err; + stackTrace = stack; + } + + final client = fixture.getSut(); + await client.captureException(error, stackTrace: stackTrace); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.exceptions?.first is SentryException, true); + expect(capturedEvent.exceptions?.first.stackTrace, isNotNull); + }); + }); + + group('SentryClient captures exception cause', () { + dynamic exception; + + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('should capture exception cause', () async { + fixture.options.addExceptionCauseExtractor( + ExceptionWithCauseExtractor(), + ); + + final cause = Object(); + exception = ExceptionWithCause(cause, null); + + final client = fixture.getSut( + eventProcessor: ExceptionGroupEventProcessor(fixture.options), + ); + await client.captureException(exception, stackTrace: null); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.exceptions?.length, 2); + + final firstException = capturedEvent.exceptions?[0]; + expect(firstException is SentryException, true); + expect(firstException?.mechanism?.source, "cause"); + expect(firstException?.mechanism?.parentId, 0); + expect(firstException?.mechanism?.exceptionId, 1); + expect(firstException?.mechanism?.isExceptionGroup, isNull); + + final secondException = capturedEvent.exceptions?[1]; + expect(secondException is SentryException, true); + expect(secondException?.mechanism?.source, null); + expect(secondException?.mechanism?.parentId, null); + expect(secondException?.mechanism?.exceptionId, 0); + expect(secondException?.mechanism?.isExceptionGroup, isTrue); + }); + + test('should capture cause stacktrace', () async { + fixture.options.addExceptionCauseExtractor( + ExceptionWithCauseExtractor(), + ); + + final cause = Object(); + final stackTrace = ''' +#0 baz (file:///pathto/test.dart:50:3) + +#1 bar (file:///pathto/test.dart:46:9) + '''; + + exception = ExceptionWithCause(cause, stackTrace); + + final client = fixture.getSut( + attachStacktrace: true, + eventProcessor: ExceptionGroupEventProcessor(fixture.options), + ); + await client.captureException(exception, stackTrace: null); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.exceptions?[0].stackTrace, isNotNull); + expect(capturedEvent.exceptions?[0].stackTrace!.frames.first.fileName, + 'test.dart'); + expect(capturedEvent.exceptions?[0].stackTrace!.frames.first.lineNo, 46); + expect(capturedEvent.exceptions?[0].stackTrace!.frames.first.colNo, 9); + }); + + test('should capture custom stacktrace', () async { + fixture.options.addExceptionStackTraceExtractor( + ExceptionWithStackTraceExtractor(), + ); + + final stackTrace = StackTrace.fromString(''' +#0 baz (file:///pathto/test.dart:50:3) + +#1 bar (file:///pathto/test.dart:46:9) + '''); + + exception = ExceptionWithStackTrace(stackTrace); + + final client = fixture.getSut( + attachStacktrace: true, + eventProcessor: ExceptionGroupEventProcessor(fixture.options), + ); + await client.captureException(exception, stackTrace: null); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.exceptions?[0].stackTrace, isNotNull); + expect(capturedEvent.exceptions?[0].stackTrace!.frames.first.fileName, + 'test.dart'); + expect(capturedEvent.exceptions?[0].stackTrace!.frames.first.lineNo, 46); + expect(capturedEvent.exceptions?[0].stackTrace!.frames.first.colNo, 9); + }); + + test('should not capture cause stacktrace when attachStacktrace is false', + () async { + fixture.options.addExceptionCauseExtractor( + ExceptionWithCauseExtractor(), + ); + + final cause = Object(); + exception = ExceptionWithCause(cause, null); + + final client = fixture.getSut( + attachStacktrace: false, + eventProcessor: ExceptionGroupEventProcessor(fixture.options), + ); + await client.captureException(exception, stackTrace: null); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.exceptions?[0].stackTrace, isNull); + }); + + test( + 'should not capture cause stacktrace when attachStacktrace is false and StackTrace.empty', + () async { + fixture.options.addExceptionCauseExtractor( + ExceptionWithCauseExtractor(), + ); + + final cause = Object(); + exception = ExceptionWithCause(cause, StackTrace.empty); + + final client = fixture.getSut( + attachStacktrace: false, + eventProcessor: ExceptionGroupEventProcessor(fixture.options), + ); + await client.captureException(exception, stackTrace: null); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.exceptions?[0].stackTrace, isNull); + }); + + test('should capture cause exception with Stackframe.current', () async { + fixture.options.addExceptionCauseExtractor( + ExceptionWithCauseExtractor(), + ); + + final cause = Object(); + exception = ExceptionWithCause(cause, null); + + final client = fixture.getSut( + attachStacktrace: true, + eventProcessor: ExceptionGroupEventProcessor(fixture.options), + ); + await client.captureException(exception, stackTrace: null); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.exceptions?[0].stackTrace, isNotNull); + }); + + test('should capture sentry frames exception', () async { + fixture.options.addExceptionCauseExtractor( + ExceptionWithCauseExtractor(), + ); + + final cause = Object(); + final stackTrace = ''' +#0 init (package:sentry/sentry.dart:46:9) +#1 bar (file:///pathto/test.dart:46:9) + +#2 capture (package:sentry/sentry.dart:46:9) + '''; + exception = ExceptionWithCause(cause, stackTrace); + + final client = fixture.getSut( + attachStacktrace: true, + eventProcessor: ExceptionGroupEventProcessor(fixture.options), + ); + await client.captureException(exception, stackTrace: null); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + final sentryFramesCount = capturedEvent.exceptions?[0].stackTrace!.frames + .where((frame) => frame.package == 'sentry') + .length; + + expect(sentryFramesCount, 2); + }); + }); + + group('SentryClient captures exception and stacktrace', () { + late Fixture fixture; + + Error error; + + dynamic exception; + + final stacktrace = ''' +#0 baz (file:///pathto/test.dart:50:3) + +#1 bar (file:///pathto/test.dart:46:9) + '''; + + setUp(() { + fixture = Fixture(); + }); + + test('should capture error', () async { + try { + throw StateError('Error'); + } on Error catch (err) { + error = err; + } + + final client = fixture.getSut(); + await client.captureException(error, stackTrace: stacktrace); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.exceptions?.first is SentryException, true); + expect(capturedEvent.exceptions?.first.stackTrace, isNotNull); + expect(capturedEvent.exceptions?.first.stackTrace!.frames.first.fileName, + 'test.dart'); + expect( + capturedEvent.exceptions?.first.stackTrace!.frames.first.lineNo, 46); + expect(capturedEvent.exceptions?.first.stackTrace!.frames.first.colNo, 9); + }); + + test('should capture exception', () async { + try { + throw Exception('Error'); + } catch (err) { + exception = err; + } + + final client = fixture.getSut(); + await client.captureException(exception, stackTrace: stacktrace); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.exceptions?.first is SentryException, true); + expect(capturedEvent.exceptions?.first.stackTrace!.frames.first.fileName, + 'test.dart'); + expect( + capturedEvent.exceptions?.first.stackTrace!.frames.first.lineNo, 46); + expect(capturedEvent.exceptions?.first.stackTrace!.frames.first.colNo, 9); + }); + + test('should capture exception with Stackframe.current', () async { + try { + throw Exception('Error'); + } catch (err) { + exception = err; + } + + final client = fixture.getSut(); + await client.captureException(exception); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.exceptions?.first.stackTrace, isNotNull); + }); + + test('should capture exception without Stackframe.current', () async { + try { + throw Exception('Error'); + } catch (err) { + exception = err; + } + + final client = fixture.getSut(attachStacktrace: false); + await client.captureException(exception); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.exceptions?.first.stackTrace, isNull); + }); + + test('should capture sentry frames exception', () async { + try { + throw Exception('Error'); + } catch (err) { + exception = err; + } + + final stackTrace = ''' +#0 baz (file:///pathto/test.dart:50:3) + +#1 bar (file:///pathto/test.dart:46:9) +#2 capture (package:sentry/sentry.dart:46:9) + '''; + + final client = fixture.getSut(); + await client.captureException(exception, stackTrace: stackTrace); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect( + capturedEvent.exceptions?.first.stackTrace!.frames + .any((frame) => frame.package == 'sentry'), + true, + ); + }); + + test('should remove sentry frames if null stackStrace', () async { + final throwable = Object(); + + final client = fixture.getSut(attachStacktrace: true); + await client.captureException(throwable, stackTrace: null); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + final sentryFramesCount = capturedEvent.exceptions?[0].stackTrace!.frames + .where((frame) => frame.package == 'sentry') + .length; + + expect(sentryFramesCount, 0); + }); + + test('should remove sentry frames if empty stackStrace', () async { + final throwable = Object(); + + final client = fixture.getSut(attachStacktrace: true); + await client.captureException(throwable, stackTrace: StackTrace.empty); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + final sentryFramesCount = capturedEvent.exceptions?[0].stackTrace!.frames + .where((frame) => frame.package == 'sentry') + .length; + + expect(sentryFramesCount, 0); + }); + }); + + group('SentryClient captures transaction', () { + late Fixture fixture; + + Error error; + + setUp(() { + fixture = Fixture(); + }); + + test( + 'when scope does not have an active transaction, trace state is set on the envelope from scope', + () async { + final client = fixture.getSut(); + final scope = Scope(fixture.options); + await client.captureEvent(SentryEvent(), scope: scope); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedTraceContext = capturedEnvelope.header.traceContext; + final capturedTraceId = capturedTraceContext?.traceId; + final propagationContextTraceId = scope.propagationContext.traceId; + + expect(capturedTraceContext, isNotNull); + expect(capturedTraceId, propagationContextTraceId); + }); + + test('attaches trace context from span if none present yet', () async { + final client = fixture.getSut(); + final spanContext = SentrySpanContext( + traceId: SentryId.newId(), + spanId: SpanId.newId(), + operation: 'op.load', + ); + final scope = Scope(fixture.options); + scope.span = SentrySpan(fixture.tracer, spanContext, MockHub()); + + final sentryEvent = SentryEvent(); + await client.captureEvent(sentryEvent, scope: scope); + + expect(fixture.transport.envelopes.length, 1); + expect(spanContext.spanId, sentryEvent.contexts.trace!.spanId); + expect(spanContext.traceId, sentryEvent.contexts.trace!.traceId); + }); + + test( + 'attaches trace context from scope if none present yet and no span on scope', + () async { + final client = fixture.getSut(); + + final scope = Scope(fixture.options); + final scopePropagationContext = scope.propagationContext; + + final sentryEvent = SentryEvent(); + await client.captureEvent(sentryEvent, scope: scope); + + expect(fixture.transport.envelopes.length, 1); + expect( + scopePropagationContext.traceId, sentryEvent.contexts.trace!.traceId); + // not checking for span id as it should be a new generated random span id + }); + + test('keeps existing trace context if already present', () async { + final client = fixture.getSut(); + + final spanContext = SentrySpanContext( + traceId: SentryId.newId(), + spanId: SpanId.newId(), + operation: 'op.load', + ); + final scope = Scope(fixture.options); + scope.span = SentrySpan(fixture.tracer, spanContext, MockHub()); + + final propagationContext = scope.propagationContext; + final preExistingSpanContext = SentryTraceContext( + traceId: SentryId.newId(), + spanId: SpanId.newId(), + operation: 'op.load'); + + final sentryEvent = SentryEvent(); + sentryEvent.contexts.trace = preExistingSpanContext; + await client.captureEvent(sentryEvent, scope: scope); + + expect(fixture.transport.envelopes.length, 1); + expect( + preExistingSpanContext.traceId, sentryEvent.contexts.trace!.traceId); + expect(preExistingSpanContext.spanId, sentryEvent.contexts.trace!.spanId); + expect(spanContext.traceId, isNot(sentryEvent.contexts.trace!.traceId)); + expect(spanContext.spanId, isNot(sentryEvent.contexts.trace!.spanId)); + expect(propagationContext.traceId, + isNot(sentryEvent.contexts.trace!.traceId)); + }); + + test( + 'uses propagation context on scope for trace header if no transaction is on scope', + () async { + final client = fixture.getSut(); + + final scope = Scope(fixture.options); + final scopePropagationContext = scope.propagationContext; + + final sentryEvent = SentryEvent(); + await client.captureEvent(sentryEvent, scope: scope); + + final capturedEnvelope = fixture.transport.envelopes.first; + final capturedTraceContext = capturedEnvelope.header.traceContext; + + expect(fixture.transport.envelopes.length, 1); + expect(scope.span, isNull); + expect(capturedTraceContext, isNotNull); + expect(scopePropagationContext.traceId, capturedTraceContext!.traceId); + }); + + test( + 'uses trace context on transaction for trace header if a transaction is on scope', + () async { + final client = fixture.getSut(); + + final spanContext = SentrySpanContext( + traceId: SentryId.newId(), + spanId: SpanId.newId(), + operation: 'op.load', + ); + final scope = Scope(fixture.options); + scope.span = SentrySpan(fixture.tracer, spanContext, MockHub()); + + final sentryEvent = SentryEvent(); + await client.captureEvent(sentryEvent, scope: scope); + + final capturedEnvelope = fixture.transport.envelopes.first; + final capturedTraceContext = capturedEnvelope.header.traceContext; + + expect(fixture.transport.envelopes.length, 1); + expect(scope.span, isNotNull); + expect(capturedTraceContext, isNotNull); + expect( + scope.span!.traceContext()!.traceId, capturedTraceContext!.traceId); + }); + + test('should contain a transaction in the envelope', () async { + try { + throw StateError('Error'); + } on Error catch (err) { + error = err; + } + + final client = fixture.getSut(); + final tr = SentryTransaction(fixture.tracer, throwable: error); + await client.captureTransaction(tr); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedTr = await transactionFromEnvelope(capturedEnvelope); + + expect(capturedTr['type'], 'transaction'); + }); + + test('should not set exception to transactions', () async { + try { + throw StateError('Error'); + } on Error catch (err) { + error = err; + } + + final client = fixture.getSut(); + final tr = SentryTransaction(fixture.tracer, throwable: error); + await client.captureTransaction(tr); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await transactionFromEnvelope(capturedEnvelope); + + expect(capturedEvent['exception'], isNull); + }); + + test('attachments not added to captured transaction per default', () async { + final attachment = SentryAttachment.fromUint8List( + Uint8List.fromList([0, 0, 0, 0]), + 'test.txt', + ); + final scope = Scope(fixture.options); + scope.addAttachment(attachment); + + final client = fixture.getSut(); + final tr = SentryTransaction(fixture.tracer); + await client.captureTransaction(tr, scope: scope); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedAttachments = capturedEnvelope.items + .where((item) => item.header.type == SentryItemType.attachment); + + expect(capturedAttachments.isEmpty, true); + }); + + test('attachments added to captured event', () async { + final attachment = SentryAttachment.fromUint8List( + Uint8List.fromList([0, 0, 0, 0]), + 'test.txt', + addToTransactions: true, + ); + final scope = Scope(fixture.options); + scope.addAttachment(attachment); + + final client = fixture.getSut(); + final tr = SentryTransaction(fixture.tracer); + await client.captureTransaction(tr, scope: scope); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedAttachments = capturedEnvelope.items + .where((item) => item.header.type == SentryItemType.attachment); + + expect(capturedAttachments.isNotEmpty, true); + }); + + test('attachments added to captured event per default', () async { + final attachment = SentryAttachment.fromUint8List( + Uint8List.fromList([0, 0, 0, 0]), + 'test.txt', + ); + final scope = Scope(fixture.options); + scope.addAttachment(attachment); + + final client = fixture.getSut(); + final event = SentryEvent(); + await client.captureEvent(event, scope: scope); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedAttachments = capturedEnvelope.items + .where((item) => item.header.type == SentryItemType.attachment); + + expect(capturedAttachments.isNotEmpty, true); + }); + + test('should return empty for when transaction is discarded', () async { + final client = fixture.getSut(eventProcessor: DropAllEventProcessor()); + final tr = SentryTransaction(fixture.tracer); + final id = await client.captureTransaction(tr); + + expect(id, SentryId.empty()); + }); + + test('transaction envelope contains dsn', () async { + final client = fixture.getSut(); + final tr = SentryTransaction(fixture.tracer); + await client.captureTransaction(tr); + + final capturedEnvelope = (fixture.transport).envelopes.first; + + expect(capturedEnvelope.header.dsn, fixture.options.dsn); + }); + }); + + group('SentryClient : apply scope to the captured event', () { + late Scope scope; + + final level = SentryLevel.error; + const transaction = '/test/scope'; + const fingerprint = ['foo', 'bar', 'baz']; + final user = SentryUser(id: '123', username: 'test'); + final crumb = Breadcrumb(message: 'bread'); + const scopeTagKey = 'scope-tag'; + const scopeTagValue = 'scope-tag-value'; + const eventTagKey = 'event-tag'; + const eventTagValue = 'event-tag-value'; + const scopeExtraKey = 'scope-extra'; + const scopeExtraValue = 'scope-extra-value'; + const eventExtraKey = 'event-extra'; + const eventExtraValue = 'event-extra-value'; + + final event = SentryEvent( + tags: const {eventTagKey: eventTagValue}, + // ignore: deprecated_member_use_from_same_package + extra: const {eventExtraKey: eventExtraValue}, + modules: const {eventExtraKey: eventExtraValue}, + level: SentryLevel.warning, + ); + + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + + scope = Scope(fixture.options) + ..level = level + ..transaction = transaction + ..fingerprint = fingerprint + ..addBreadcrumb(crumb) + ..setTag(scopeTagKey, scopeTagValue) + // ignore: deprecated_member_use_from_same_package + ..setExtra(scopeExtraKey, scopeExtraValue) + ..replayId = SentryId.fromId('1'); + + scope.setUser(user); + }); + + test('should apply the scope to event', () async { + final client = fixture.getSut(); + await client.captureEvent(event, scope: scope); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.user?.id, user.id); + expect(capturedEvent.level!.name, SentryLevel.error.name); + expect(capturedEvent.transaction, transaction); + expect(capturedEvent.fingerprint, fingerprint); + expect(capturedEvent.breadcrumbs?.first.toJson(), crumb.toJson()); + expect(capturedEvent.tags, { + scopeTagKey: scopeTagValue, + eventTagKey: eventTagValue, + }); + // ignore: deprecated_member_use_from_same_package + expect(capturedEvent.extra, { + scopeExtraKey: scopeExtraValue, + eventExtraKey: eventExtraValue, + }); + expect( + capturedEnvelope.header.traceContext?.replayId, SentryId.fromId('1')); + }); + + test('should apply the scope to feedback event', () async { + final client = fixture.getSut(); + final feedback = fixture.fakeFeedback(); + await client.captureFeedback(feedback, scope: scope); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.user?.id, user.id); + expect(capturedEvent.level!.name, SentryLevel.error.name); + expect(capturedEvent.transaction, transaction); + expect(capturedEvent.fingerprint, fingerprint); + expect(capturedEvent.breadcrumbs, isNull); + expect(capturedEvent.tags, { + scopeTagKey: scopeTagValue, + }); + // ignore: deprecated_member_use_from_same_package + expect(capturedEvent.extra, isNull); + }); + }); + + group('SentryClient : apply partial scope to the captured event', () { + late Fixture fixture; + + late String transaction; + late String eventTransaction; + late List fingerprint; + late List eventFingerprint; + late SentryUser user; + late Breadcrumb crumb; + late SentryUser eventUser; + late List eventCrumbs; + late SentryEvent event; + + Future createScope(SentryOptions options) async { + final scope = Scope(options) + ..transaction = transaction + ..fingerprint = fingerprint; + await scope.addBreadcrumb(crumb); + await scope.setUser(user); + return scope; + } + + setUp(() { + fixture = Fixture(); + + transaction = '/test/scope'; + eventTransaction = '/event/transaction'; + fingerprint = ['foo', 'bar', 'baz']; + eventFingerprint = ['123', '456', '798']; + user = SentryUser(id: '123'); + crumb = Breadcrumb(message: 'bread'); + eventUser = SentryUser(id: '987'); + eventCrumbs = [Breadcrumb(message: 'bread')]; + event = SentryEvent( + level: SentryLevel.warning, + transaction: eventTransaction, + user: eventUser, + fingerprint: eventFingerprint, + breadcrumbs: eventCrumbs, + ); + }); + + test('should not apply the scope to non null event fields', () async { + final client = fixture.getSut(sendDefaultPii: true); + final scope = await createScope(fixture.options); + + await client.captureEvent(event, scope: scope); + + final capturedEnvelope = fixture.transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.user!.id, eventUser.id); + expect(capturedEvent.level!.name, SentryLevel.warning.name); + expect(capturedEvent.transaction, eventTransaction); + expect(capturedEvent.fingerprint, eventFingerprint); + expect(capturedEvent.breadcrumbs?.map((e) => e.toJson()), + eventCrumbs.map((e) => e.toJson())); + }); + + test('should apply the scope user to null event user fields', () async { + final client = fixture.getSut(sendDefaultPii: true); + final scope = await createScope(fixture.options); + + await scope.setUser(SentryUser(id: '987')); + + event.user = SentryUser(id: '123', username: 'foo bar'); + await client.captureEvent(event, scope: scope); + + final capturedEnvelope = fixture.transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.user!.id, '123'); + expect(capturedEvent.user!.username, 'foo bar'); + expect(capturedEvent.level!.name, SentryLevel.warning.name); + expect(capturedEvent.transaction, eventTransaction); + expect(capturedEvent.fingerprint, eventFingerprint); + expect(capturedEvent.breadcrumbs?.map((e) => e.toJson()), + eventCrumbs.map((e) => e.toJson())); + }); + + test('merge scope user and event user extra', () async { + final client = fixture.getSut(sendDefaultPii: true); + final scope = await createScope(fixture.options); + + await scope.setUser( + SentryUser( + id: 'id', + data: { + 'foo': 'bar', + 'bar': 'foo', + }, + ), + ); + + event.user = SentryUser( + id: 'id', + data: { + 'foo': 'this bar is more important', + 'event': 'Really important event' + }, + ); + await client.captureEvent(event, scope: scope); + + final capturedEnvelope = fixture.transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.user?.data?['foo'], 'this bar is more important'); + expect(capturedEvent.user?.data?['bar'], 'foo'); + expect(capturedEvent.user?.data?['event'], 'Really important event'); + }); + }); + + group('SentryClient: user & user ip', () { + late Fixture fixture; + late SentryUser fakeUser; + + setUp(() { + fixture = Fixture(); + fakeUser = getFakeUser(); + }); + + test('event has no user and sendDefaultPii = true', () async { + final client = fixture.getSut(sendDefaultPii: true); + final fakeEvent = SentryEvent(); + expect(fakeEvent.user, isNull); + + await client.captureEvent(fakeEvent); + + final capturedEnvelope = fixture.transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(fixture.transport.envelopes.length, 1); + expect(capturedEvent.user, isNotNull); + expect(capturedEvent.user?.ipAddress, defaultIpAddress); + }); + + test('event has no user and sendDefaultPii = false', () async { + final client = fixture.getSut(sendDefaultPii: false); + var fakeEvent = SentryEvent(); + expect(fakeEvent.user, isNull); + + await client.captureEvent(fakeEvent); + + final capturedEnvelope = fixture.transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(fixture.transport.envelopes.length, 1); + expect(capturedEvent.user, isNull); + }); + + test('event has a user with IP address', () async { + final client = fixture.getSut(sendDefaultPii: true); + final fakeEvent = getFakeEvent(); + + expect(fakeEvent.user?.ipAddress, isNotNull); + await client.captureEvent(fakeEvent); + + final capturedEnvelope = fixture.transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(fixture.transport.envelopes.length, 1); + expect(capturedEvent.user, isNotNull); + // fakeEvent has a user which is not null + expect(capturedEvent.user?.ipAddress, fakeEvent.user!.ipAddress); + expect(capturedEvent.user?.id, fakeEvent.user!.id); + expect(capturedEvent.user?.email, fakeEvent.user!.email); + }); + + test('event has a user without IP address and sendDefaultPii = true', + () async { + final client = fixture.getSut(sendDefaultPii: true); + final fakeEvent = getFakeEvent(); + fakeEvent.user = fakeUser; + + expect(fakeEvent.user?.ipAddress, isNull); + + await client.captureEvent(fakeEvent); + + final capturedEnvelope = fixture.transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(fixture.transport.envelopes.length, 1); + expect(capturedEvent.user, isNotNull); + expect(capturedEvent.user?.ipAddress, defaultIpAddress); + expect(capturedEvent.user?.id, fakeUser.id); + expect(capturedEvent.user?.email, fakeUser.email); + }); + + test('event has a user without IP address and sendDefaultPii = false', + () async { + final client = fixture.getSut(sendDefaultPii: false); + final fakeEvent = getFakeEvent(); + fakeEvent.user = fakeUser; + + expect(fakeEvent.user?.ipAddress, isNull); + + await client.captureEvent(fakeEvent); + + final capturedEnvelope = fixture.transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(fixture.transport.envelopes.length, 1); + expect(capturedEvent.user, isNotNull); + expect(capturedEvent.user?.ipAddress, isNull); + expect(capturedEvent.user?.id, fakeUser.id); + expect(capturedEvent.user?.email, fakeUser.email); + }); + }); + + group('SentryClient sampling', () { + late Fixture fixture; + late SentryEvent fakeEvent; + + setUp(() { + fixture = Fixture(); + fakeEvent = getFakeEvent(); + }); + + test('captures event, sample rate is 100% enabled', () async { + final client = fixture.getSut(sampleRate: 1.0); + await client.captureEvent(fakeEvent); + + expect(fixture.transport.called(1), true); + }); + + test('do not capture event, sample rate is 0% disabled', () async { + final client = fixture.getSut(sampleRate: 0.0); + await client.captureEvent(fakeEvent); + + expect(fixture.transport.called(0), true); + }); + + test('captures event, sample rate is null, disabled', () async { + final client = fixture.getSut(); + await client.captureEvent(fakeEvent); + + expect(fixture.transport.called(1), true); + }); + + test('capture feedback event, sample rate is 0% disabled', () async { + final client = fixture.getSut(sampleRate: 0.0); + + final fakeFeedback = fixture.fakeFeedback(); + await client.captureFeedback(fakeFeedback); + + expect(fixture.transport.called(1), true); + }); + }); + + group('SentryClient ignored errors', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + fixture.options.ignoreErrors = ["my-error", "^error-.*\$"]; + }); + + test('drop event if error message fully matches ignoreErrors value', + () async { + final event = SentryEvent(message: SentryMessage("my-error")); + + final client = fixture.getSut(); + await client.captureEvent(event); + + expect(fixture.transport.called(0), true); + }); + + test('drop event if error message partially matches ignoreErrors value', + () async { + final event = SentryEvent(message: SentryMessage("this is my-error-foo")); + + final client = fixture.getSut(); + await client.captureEvent(event); + + expect(fixture.transport.called(0), true); + }); + + test( + 'drop event if error message partially matches ignoreErrors regex value', + () async { + final event = SentryEvent(message: SentryMessage("error-test message")); + + final client = fixture.getSut(); + await client.captureEvent(event); + + expect(fixture.transport.called(0), true); + }); + + test('send event if error message does not match ignoreErrors value', + () async { + final event = SentryEvent(message: SentryMessage("warning")); + + final client = fixture.getSut(); + await client.captureEvent(event); + + expect(fixture.transport.called(1), true); + }); + + test('send event if no values are set for ignoreErrors', () async { + fixture.options.ignoreErrors = []; + final event = SentryEvent(message: SentryMessage("this is a test event")); + + final client = fixture.getSut(); + await client.captureEvent(event); + + expect(fixture.transport.called(1), true); + }); + }); + + group('SentryClient ignored transactions', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + fixture.options.ignoreTransactions = [ + "my-transaction", + "^transaction-.*\$" + ]; + }); + + test('drop transaction if name fully matches ignoreTransaction value', + () async { + final client = fixture.getSut(); + final fakeTransaction = fixture.fakeTransaction(); + fakeTransaction.tracer.name = "my-transaction"; + await client.captureTransaction(fakeTransaction); + + expect(fixture.transport.called(0), true); + }); + + test('drop transaction if name partially matches ignoreTransaction value', + () async { + final client = fixture.getSut(); + final fakeTransaction = fixture.fakeTransaction(); + fakeTransaction.tracer.name = "this is a my-transaction-test"; + await client.captureTransaction(fakeTransaction); + + expect(fixture.transport.called(0), true); + }); + + test( + 'drop transaction if name partially matches ignoreTransaction regex value', + () async { + final client = fixture.getSut(); + final fakeTransaction = fixture.fakeTransaction(); + fakeTransaction.tracer.name = "transaction-test message"; + await client.captureTransaction(fakeTransaction); + + expect(fixture.transport.called(0), true); + }); + + test('send transaction if name does not match ignoreTransaction value', + () async { + final client = fixture.getSut(); + final fakeTransaction = fixture.fakeTransaction(); + fakeTransaction.tracer.name = "capture"; + await client.captureTransaction(fakeTransaction); + + expect(fixture.transport.called(1), true); + }); + + test('send transaction if no values are set for ignoreTransaction', + () async { + fixture.options.ignoreTransactions = []; + final client = fixture.getSut(); + final fakeTransaction = fixture.fakeTransaction(); + fakeTransaction.tracer.name = "this is a test transaction"; + await client.captureTransaction(fakeTransaction); + + expect(fixture.transport.called(1), true); + }); + }); + + group('SentryClient ignored exceptions', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('addExceptionFilterForType drops matching error event throwable', + () async { + fixture.options.addExceptionFilterForType(ExceptionWithCause); + + final throwable = ExceptionWithCause(Error(), StackTrace.current); + final event = SentryEvent(throwable: throwable); + + final client = fixture.getSut(); + await client.captureEvent(event); + + expect(fixture.transport.called(0), true); + }); + + test('record ignored exceptions dropping event', () async { + fixture.options.addExceptionFilterForType(ExceptionWithCause); + + final throwable = ExceptionWithCause(Error(), StackTrace.current); + final event = SentryEvent(throwable: throwable); + + final client = fixture.getSut(); + await client.captureEvent(event); + + expect(fixture.recorder.discardedEvents.first.reason, + DiscardReason.eventProcessor); + expect( + fixture.recorder.discardedEvents.first.category, DataCategory.error); + }); + }); + + group('SentryClient before send feedback', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('before send feedback drops event', () async { + final client = fixture.getSut( + beforeSendFeedback: beforeSendFeedbackCallbackDropEvent); + final fakeFeedback = fixture.fakeFeedback(); + await client.captureFeedback(fakeFeedback); + + expect(fixture.transport.called(0), true); + }); + + test('async before send feedback drops event', () async { + final client = fixture.getSut( + beforeSendFeedback: asyncBeforeSendFeedbackCallbackDropEvent); + final fakeFeedback = fixture.fakeFeedback(); + await client.captureFeedback(fakeFeedback); + + expect(fixture.transport.called(0), true); + }); + + test( + 'before send feedback returns an feedback event and feedback event is captured', + () async { + final client = + fixture.getSut(beforeSendFeedback: beforeSendFeedbackCallback); + final fakeFeedback = fixture.fakeFeedback(); + await client.captureFeedback(fakeFeedback); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final feedbackEvent = await eventFromEnvelope(capturedEnvelope); + + expect(feedbackEvent.tags!.containsKey('theme'), true); + }); + + test('thrown error is handled', () async { + fixture.options.automatedTestMode = false; + final exception = Exception("before send exception"); + final beforeSendFeedbackCallback = (SentryEvent event, Hint hint) { + throw exception; + }; + + final client = fixture.getSut( + beforeSendFeedback: beforeSendFeedbackCallback, debug: true); + final fakeFeedback = fixture.fakeFeedback(); + await client.captureFeedback(fakeFeedback); + + expect(fixture.loggedException, exception); + expect(fixture.loggedLevel, SentryLevel.error); + }); + }); + + group('SentryClient before send transaction', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('before send transaction drops event', () async { + final client = fixture.getSut( + beforeSendTransaction: beforeSendTransactionCallbackDropEvent); + final fakeTransaction = fixture.fakeTransaction(); + await client.captureTransaction(fakeTransaction); + + expect(fixture.transport.called(0), true); + }); + + test('async before send transaction drops event', () async { + final client = fixture.getSut( + beforeSendTransaction: asyncBeforeSendTransactionCallbackDropEvent); + final fakeTransaction = fixture.fakeTransaction(); + await client.captureTransaction(fakeTransaction); + + expect(fixture.transport.called(0), true); + }); + + test( + 'before send transaction returns an transaction and transaction is captured', + () async { + final client = + fixture.getSut(beforeSendTransaction: beforeSendTransactionCallback); + final fakeTransaction = fixture.fakeTransaction(); + await client.captureTransaction(fakeTransaction); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final transaction = await transactionFromEnvelope(capturedEnvelope); + + expect(transaction['tags']!.containsKey('theme'), true); + expect(transaction['extra']!.containsKey('host'), true); + expect(transaction['sdk']!['integrations'].contains('testIntegration'), + true); + expect( + transaction['sdk']!['packages'] + .any((element) => element['name'] == 'test-pkg'), + true, + ); + expect( + transaction['breadcrumbs']! + .any((element) => element['message'] == 'processor crumb'), + true, + ); + }); + + test('thrown error is handled', () async { + fixture.options.automatedTestMode = false; + final exception = Exception("before send exception"); + final beforeSendTransactionCallback = + (SentryTransaction event, Hint hint) { + throw exception; + }; + + fixture.options.automatedTestMode = false; + final client = fixture.getSut( + beforeSendTransaction: beforeSendTransactionCallback, debug: true); + final fakeTransaction = fixture.fakeTransaction(); + await client.captureTransaction(fakeTransaction); + + expect(fixture.loggedException, exception); + expect(fixture.loggedLevel, SentryLevel.error); + }); + }); + + group('SentryClient before send', () { + late Fixture fixture; + late SentryEvent fakeEvent; + + setUp(() { + fixture = Fixture(); + fakeEvent = getFakeEvent(); + }); + + test('before send drops event', () async { + final client = fixture.getSut(beforeSend: beforeSendCallbackDropEvent); + await client.captureEvent(fakeEvent); + + expect(fixture.transport.called(0), true); + }); + + test('async before send drops event', () async { + final client = + fixture.getSut(beforeSend: asyncBeforeSendCallbackDropEvent); + await client.captureEvent(fakeEvent); + + expect(fixture.transport.called(0), true); + }); + + test('before send returns an event and event is captured', () async { + final client = fixture.getSut(beforeSend: beforeSendCallback); + await client.captureEvent(fakeEvent); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final event = await eventFromEnvelope(capturedEnvelope); + + expect(event.tags!.containsKey('theme'), true); + // ignore: deprecated_member_use_from_same_package + expect(event.extra!.containsKey('host'), true); + expect(event.modules!.containsKey('core'), true); + expect(event.sdk!.integrations.contains('testIntegration'), true); + expect( + event.sdk!.packages.any((element) => element.name == 'test-pkg'), + true, + ); + expect( + event.breadcrumbs! + .any((element) => element.message == 'processor crumb'), + true, + ); + expect(event.fingerprint!.contains('process'), true); + }); + + test('thrown error is handled', () async { + fixture.options.automatedTestMode = false; + final exception = Exception("before send exception"); + final beforeSendCallback = (SentryEvent event, Hint hint) { + throw exception; + }; + + fixture.options.automatedTestMode = false; + final client = + fixture.getSut(beforeSend: beforeSendCallback, debug: true); + + await client.captureEvent(fakeEvent); + + expect(fixture.loggedException, exception); + expect(fixture.loggedLevel, SentryLevel.error); + }); + }); + + group('EventProcessors', () { + late Fixture fixture; + late SentryEvent fakeEvent; + + setUp(() { + fixture = Fixture(); + fakeEvent = getFakeEvent(); + fixture.options.addEventProcessor(FunctionEventProcessor((event, hint) { + event.tags = {'theme': 'material'}; + // ignore: deprecated_member_use_from_same_package + event.extra?['host'] = '0.0.0.1'; + event.modules?.addAll({'core': '1.0'}); + event.breadcrumbs?.add(Breadcrumb(message: 'processor crumb')); + event.fingerprint?.add('process'); + event.sdk?.addIntegration('testIntegration'); + event.sdk?.addPackage('test-pkg', '1.0'); + + return event; + })); + }); + + test('should execute eventProcessors for event', () async { + final client = fixture.getSut(); + await client.captureEvent(fakeEvent); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final event = await eventFromEnvelope(capturedEnvelope); + + expect(event.tags!.containsKey('theme'), true); + // ignore: deprecated_member_use_from_same_package + expect(event.extra!.containsKey('host'), true); + expect(event.modules!.containsKey('core'), true); + expect(event.sdk!.integrations.contains('testIntegration'), true); + expect( + event.sdk!.packages.any((element) => element.name == 'test-pkg'), + true, + ); + expect( + event.breadcrumbs! + .any((element) => element.message == 'processor crumb'), + true, + ); + expect(event.fingerprint!.contains('process'), true); + }); + + test('should execute eventProcessors for feedback', () async { + final client = fixture.getSut(); + final fakeFeedback = fixture.fakeFeedback(); + await client.captureFeedback(fakeFeedback); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final event = await eventFromEnvelope(capturedEnvelope); + + expect(event.tags?.containsKey('theme'), true); + }); + + test('should pass hint to eventProcessors for event', () async { + final myHint = Hint(); + myHint.set('string', 'hint'); + + var executed = false; + + final client = + fixture.getSut(eventProcessor: FunctionEventProcessor((event, hint) { + expect(myHint, hint); + executed = true; + return event; + })); + + await client.captureEvent(fakeEvent, hint: myHint); + + expect(executed, true); + }); + + test('should pass hint to eventProcessors for feedback', () async { + final myHint = Hint(); + myHint.set('string', 'hint'); + + var executed = false; + + final client = + fixture.getSut(eventProcessor: FunctionEventProcessor((event, hint) { + expect(myHint, hint); + executed = true; + return event; + })); + + final fakeFeedback = fixture.fakeFeedback(); + await client.captureFeedback(fakeFeedback, hint: myHint); + + expect(executed, true); + }); + + test('should create hint when none was provided for event', () async { + var executed = false; + + final client = + fixture.getSut(eventProcessor: FunctionEventProcessor((event, hint) { + expect(hint, isNotNull); + executed = true; + return event; + })); + + await client.captureEvent(fakeEvent); + + expect(executed, true); + }); + + test('should create hint when none was provided for feedback event', + () async { + var executed = false; + + final client = + fixture.getSut(eventProcessor: FunctionEventProcessor((event, hint) { + expect(hint, isNotNull); + executed = true; + return event; + })); + + final fakeFeedback = fixture.fakeFeedback(); + await client.captureFeedback(fakeFeedback); + + expect(executed, true); + }); + + test('event processor drops the event', () async { + final client = fixture.getSut(eventProcessor: DropAllEventProcessor()); + + await client.captureEvent(fakeEvent); + + expect(fixture.transport.called(0), true); + }); + + test('event processor drops the feedback event', () async { + final client = fixture.getSut(eventProcessor: DropAllEventProcessor()); + + final fakeFeedback = fixture.fakeFeedback(); + await client.captureFeedback(fakeFeedback); + + expect(fixture.transport.called(0), true); + }); + }); + + group('SentryClient captures feedback', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('should capture feedback as event', () async { + final client = fixture.getSut(); + + final feedback = fixture.fakeFeedback(); + await client.captureFeedback(feedback); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final envelopeItem = capturedEnvelope.items.first; + final envelopeEvent = envelopeItem.originalObject as SentryEvent?; + + expect(envelopeItem, isNotNull); + expect(envelopeEvent, isNotNull); + + expect(envelopeItem.header.type, 'feedback'); + + expect(envelopeEvent?.type, 'feedback'); + expect(envelopeEvent?.contexts.feedback?.toJson(), feedback.toJson()); + expect(envelopeEvent?.level, SentryLevel.info); + }); + + test('should cap feedback messages to max 4096 characters', () async { + final client = fixture.getSut(); + final feedback = fixture.fakeFeedback(); + feedback.message = 'a' * 4096 + 'b' * 4096; + await client.captureFeedback(feedback); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.contexts.feedback?.message, 'a' * 4096); + }); + }); + + group('SentryClient captureLog', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + SentryLog givenLog() { + return SentryLog( + timestamp: DateTime.now(), + traceId: SentryId.newId(), + level: SentryLogLevel.info, + body: 'test', + attributes: { + 'attribute': SentryLogAttribute.string('value'), + }, + ); + } + + test('sets log batcher on options when logs are enabled', () async { + expect(fixture.options.logBatcher is NoopLogBatcher, true); + + fixture.options.enableLogs = true; + fixture.getSut(); + + expect(fixture.options.logBatcher is NoopLogBatcher, false); + }); + + test('disabled by default', () async { + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + final log = givenLog(); + + await client.captureLog(log); + + final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; + expect(mockLogBatcher.addLogCalls, isEmpty); + }); + + test('should capture logs as envelope', () async { + fixture.options.enableLogs = true; + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + final log = givenLog(); + + await client.captureLog(log); + + final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; + expect(mockLogBatcher.addLogCalls.length, 1); + + final capturedLog = mockLogBatcher.addLogCalls.first; + + expect(capturedLog.traceId, log.traceId); + expect(capturedLog.level, log.level); + expect(capturedLog.body, log.body); + expect(capturedLog.attributes['attribute']?.value, + log.attributes['attribute']?.value); + }); + + test('should add additional info to attributes', () async { + fixture.options.enableLogs = true; + fixture.options.environment = 'test-environment'; + fixture.options.release = 'test-release'; + + final log = givenLog(); + + final scope = Scope(fixture.options); + final span = MockSpan(); + scope.span = span; + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + await client.captureLog(log, scope: scope); + + final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; + expect(mockLogBatcher.addLogCalls.length, 1); + final capturedLog = mockLogBatcher.addLogCalls.first; + + expect( + capturedLog.attributes['sentry.sdk.name']?.value, + fixture.options.sdk.name, + ); + expect( + capturedLog.attributes['sentry.sdk.name']?.type, + 'string', + ); + expect( + capturedLog.attributes['sentry.sdk.version']?.value, + fixture.options.sdk.version, + ); + expect( + capturedLog.attributes['sentry.sdk.version']?.type, + 'string', + ); + expect( + capturedLog.attributes['sentry.environment']?.value, + fixture.options.environment, + ); + expect( + capturedLog.attributes['sentry.environment']?.type, + 'string', + ); + expect( + capturedLog.attributes['sentry.release']?.value, + fixture.options.release, + ); + expect( + capturedLog.attributes['sentry.release']?.type, + 'string', + ); + expect( + capturedLog.attributes['sentry.trace.parent_span_id']?.value, + span.context.spanId.toString(), + ); + expect( + capturedLog.attributes['sentry.trace.parent_span_id']?.type, + 'string', + ); + }); + + test('should add user info to attributes', () async { + fixture.options.enableLogs = true; + + final log = givenLog(); + final scope = Scope(fixture.options); + final user = SentryUser( + id: '123', + email: 'test@test.com', + name: 'test-name', + ); + await scope.setUser(user); + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + await client.captureLog(log, scope: scope); + + final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; + expect(mockLogBatcher.addLogCalls.length, 1); + final capturedLog = mockLogBatcher.addLogCalls.first; + + expect( + capturedLog.attributes['user.id']?.value, + user.id, + ); + expect( + capturedLog.attributes['user.id']?.type, + 'string', + ); + + expect( + capturedLog.attributes['user.name']?.value, + user.name, + ); + expect( + capturedLog.attributes['user.name']?.type, + 'string', + ); + + expect( + capturedLog.attributes['user.email']?.value, + user.email, + ); + expect( + capturedLog.attributes['user.email']?.type, + 'string', + ); + }); + + test('should set trace id from propagation context', () async { + fixture.options.enableLogs = true; + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + final log = givenLog(); + final scope = Scope(fixture.options); + + await client.captureLog(log, scope: scope); + + final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; + expect(mockLogBatcher.addLogCalls.length, 1); + final capturedLog = mockLogBatcher.addLogCalls.first; + + expect(capturedLog.traceId, scope.propagationContext.traceId); + }); + + test( + '$BeforeSendLogCallback returning null drops the log and record it as lost', + () async { + fixture.options.enableLogs = true; + fixture.options.beforeSendLog = (log) => null; + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + final log = givenLog(); + + await client.captureLog(log); + + final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; + expect(mockLogBatcher.addLogCalls.length, 0); + + expect( + fixture.recorder.discardedEvents.first.reason, + DiscardReason.beforeSend, + ); + expect( + fixture.recorder.discardedEvents.first.category, + DataCategory.logItem, + ); + }); + + test('$BeforeSendLogCallback returning a log modifies it', () async { + fixture.options.enableLogs = true; + fixture.options.beforeSendLog = (log) { + log.body = 'modified'; + return log; + }; + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + final log = givenLog(); + + await client.captureLog(log); + + final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; + expect(mockLogBatcher.addLogCalls.length, 1); + final capturedLog = mockLogBatcher.addLogCalls.first; + + expect(capturedLog.body, 'modified'); + }); + + test('$BeforeSendLogCallback returning a log async modifies it', () async { + fixture.options.enableLogs = true; + fixture.options.beforeSendLog = (log) async { + await Future.delayed(Duration(milliseconds: 100)); + log.body = 'modified'; + return log; + }; + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + final log = givenLog(); + + await client.captureLog(log); + + final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; + expect(mockLogBatcher.addLogCalls.length, 1); + final capturedLog = mockLogBatcher.addLogCalls.first; + + expect(capturedLog.body, 'modified'); + }); + + test('$BeforeSendLogCallback throwing is caught', () async { + fixture.options.enableLogs = true; + fixture.options.automatedTestMode = false; + + fixture.options.beforeSendLog = (log) { + throw Exception('test'); + }; + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + final log = givenLog(); + await client.captureLog(log); + + final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; + expect(mockLogBatcher.addLogCalls.length, 1); + final capturedLog = mockLogBatcher.addLogCalls.first; + + expect(capturedLog.body, 'test'); + }); + + test('OnBeforeCaptureLog lifecycle event is called', () async { + fixture.options.enableLogs = true; + fixture.options.environment = 'test-environment'; + fixture.options.release = 'test-release'; + + final log = givenLog(); + + final scope = Scope(fixture.options); + final span = MockSpan(); + scope.span = span; + + final client = fixture.getSut(); + fixture.options.logBatcher = MockLogBatcher(); + + client.lifeCycleRegistry.registerCallback((event) { + event.log.attributes['test'] = SentryLogAttribute.string('test-value'); + }); + + await client.captureLog(log, scope: scope); + + final mockLogBatcher = fixture.options.logBatcher as MockLogBatcher; + expect(mockLogBatcher.addLogCalls.length, 1); + final capturedLog = mockLogBatcher.addLogCalls.first; + + expect(capturedLog.attributes['test']?.value, "test-value"); + expect(capturedLog.attributes['test']?.type, 'string'); + }); + }); + + group('SentryClient captures envelope', () { + late Fixture fixture; + final fakeEnvelope = getFakeEnvelope(); + + setUp(() { + fixture = Fixture(); + }); + + test('should capture envelope', () async { + final client = fixture.getSut(); + await client.captureEnvelope(fakeEnvelope); + + final capturedEnvelope = (fixture.transport).envelopes.first; + + expect(capturedEnvelope, fakeEnvelope); + }); + }); + + group('ClientReportTransport', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('set on options on init', () async { + fixture.getSut( + eventProcessor: DropAllEventProcessor(), + provideMockRecorder: false, + ); + + expect(fixture.options.transport is ClientReportTransport, true); + }); + + test('has rateLimiter with http transport', () async { + fixture.getSut( + eventProcessor: DropAllEventProcessor(), + provideMockRecorder: false, + transport: NoOpTransport(), // this will set http transport + ); + + expect(fixture.options.transport is ClientReportTransport, true); + final crt = fixture.options.transport as ClientReportTransport; + expect(crt.rateLimiter, isNotNull); + }); + + test('does not have rateLimiter without http transport', () async { + fixture.getSut( + eventProcessor: DropAllEventProcessor(), + provideMockRecorder: false, + transport: MockTransport(), + ); + + expect(fixture.options.transport is ClientReportTransport, true); + final crt = fixture.options.transport as ClientReportTransport; + expect(crt.rateLimiter, isNull); + }); + }); + + group('ClientReportRecorder', () { + late Fixture fixture; + late SentryEvent fakeEvent; + + setUp(() { + fixture = Fixture(); + fakeEvent = getFakeEvent(); + }); + + test('recorder is not noop if client reports are enabled', () async { + fixture.options.sendClientReports = true; + + fixture.getSut( + eventProcessor: DropAllEventProcessor(), + provideMockRecorder: false, + ); + + expect(fixture.options.recorder is NoOpClientReportRecorder, false); + }); + + test('recorder is noop if client reports are disabled', () { + fixture.options.sendClientReports = false; + + fixture.getSut( + eventProcessor: DropAllEventProcessor(), + provideMockRecorder: false, + ); + + expect(fixture.options.recorder is NoOpClientReportRecorder, true); + }); + + test('record event processor dropping event', () async { + bool secondProcessorCalled = false; + fixture.options.addEventProcessor(DropAllEventProcessor()); + fixture.options.addEventProcessor(FunctionEventProcessor((event, hint) { + secondProcessorCalled = true; + return event; + })); + final client = fixture.getSut(); + + await client.captureEvent(fakeEvent); + + expect(fixture.recorder.discardedEvents.first.reason, + DiscardReason.eventProcessor); + expect( + fixture.recorder.discardedEvents.first.category, DataCategory.error); + expect(secondProcessorCalled, isFalse); + }); + + test('record event processor dropping transaction', () async { + final sut = fixture.getSut(eventProcessor: DropAllEventProcessor()); + final transaction = SentryTransaction(fixture.tracer); + fixture.tracer.startChild('child1'); + fixture.tracer.startChild('child2'); + fixture.tracer.startChild('child3'); + + await sut.captureTransaction(transaction); + + expect(fixture.recorder.discardedEvents.length, 2); + + final spanCount = fixture.recorder.discardedEvents + .firstWhere((element) => + element.category == DataCategory.span && + element.reason == DiscardReason.eventProcessor) + .quantity; + expect(spanCount, 4); + }); + + test('record event processor dropping feedback', () async { + final client = fixture.getSut(eventProcessor: DropAllEventProcessor()); + final feedback = fixture.fakeFeedback(); + await client.captureFeedback(feedback); + + expect(fixture.recorder.discardedEvents.first.category, + DataCategory.feedback); + expect(fixture.recorder.discardedEvents.first.reason, + DiscardReason.eventProcessor); + }); + + test('record event processor dropping partially spans', () async { + final numberOfSpansDropped = 2; + final sut = fixture.getSut( + eventProcessor: DropSpansEventProcessor(numberOfSpansDropped)); + final transaction = SentryTransaction(fixture.tracer); + fixture.tracer.startChild('child1'); + fixture.tracer.startChild('child2'); + fixture.tracer.startChild('child3'); + + await sut.captureTransaction(transaction); + + expect(fixture.recorder.discardedEvents.length, 1); + + final spanCount = fixture.recorder.discardedEvents + .firstWhere((element) => + element.category == DataCategory.span && + element.reason == DiscardReason.eventProcessor) + .quantity; + expect(spanCount, numberOfSpansDropped); + }); + + test('beforeSendTransaction correctly records partially dropped spans', + () async { + final sut = fixture.getSut(); + final transaction = SentryTransaction(fixture.tracer); + fixture.tracer.startChild('child1'); + fixture.tracer.startChild('child2'); + fixture.tracer.startChild('child3'); + + fixture.options.beforeSendTransaction = (transaction, hint) { + if (transaction.tracer == fixture.tracer) { + return null; + } + return transaction; + }; + + await sut.captureTransaction(transaction); + + expect(fixture.recorder.discardedEvents.length, 2); + + final spanCount = fixture.recorder.discardedEvents + .firstWhere((element) => + element.category == DataCategory.span && + element.reason == DiscardReason.beforeSend) + .quantity; + expect(spanCount, 4); + }); + + test('beforeSendTransaction correctly records partially dropped spans', + () async { + final sut = fixture.getSut(); + final transaction = SentryTransaction(fixture.tracer); + fixture.tracer.startChild('child1'); + fixture.tracer.startChild('child2'); + fixture.tracer.startChild('child3'); + + fixture.options.beforeSendTransaction = (transaction, hint) { + if (transaction.tracer == fixture.tracer) { + transaction.spans + .removeWhere((element) => element.context.operation == 'child2'); + return transaction; + } + return transaction; + }; + + await sut.captureTransaction(transaction); + + // we didn't drop the whole transaction, we only have 1 event for the dropped spans + expect(fixture.recorder.discardedEvents.length, 1); + + // tracer has 3 span children and we dropped 1 of them + final spanCount = fixture.recorder.discardedEvents + .firstWhere((element) => + element.category == DataCategory.span && + element.reason == DiscardReason.beforeSend) + .quantity; + expect(spanCount, 1); + }); + + test('record event processor dropping transaction', () async { + final client = fixture.getSut(eventProcessor: DropAllEventProcessor()); + + final context = SentryTransactionContext('name', 'op'); + final tracer = SentryTracer(context, MockHub()); + final transaction = SentryTransaction(tracer); + + await client.captureTransaction(transaction); + + expect(fixture.recorder.discardedEvents.first.reason, + DiscardReason.eventProcessor); + expect(fixture.recorder.discardedEvents.first.category, + DataCategory.transaction); + }); + + test('record beforeSend dropping event', () async { + final client = fixture.getSut(); + + fixture.options.beforeSend = fixture.droppingBeforeSend; + + await client.captureEvent(fakeEvent); + + expect(fixture.recorder.discardedEvents.first.reason, + DiscardReason.beforeSend); + expect( + fixture.recorder.discardedEvents.first.category, DataCategory.error); + }); + + test('record beforeSend dropping feedback', () async { + final client = fixture.getSut(); + + fixture.options.beforeSendFeedback = fixture.droppingBeforeSend; + + final feedback = fixture.fakeFeedback(); + await client.captureFeedback(feedback); + + expect(fixture.recorder.discardedEvents.first.reason, + DiscardReason.beforeSend); + expect(fixture.recorder.discardedEvents.first.category, + DataCategory.feedback); + }); + + test('record sample rate dropping event', () async { + final client = fixture.getSut(sampleRate: 0.0); + + fixture.options.beforeSend = fixture.droppingBeforeSend; + + await client.captureEvent(fakeEvent); + + expect(fixture.recorder.discardedEvents.first.reason, + DiscardReason.sampleRate); + expect( + fixture.recorder.discardedEvents.first.category, DataCategory.error); + }); + + test('record sample rate not dropping feedback', () async { + final client = fixture.getSut(sampleRate: 0.0); + + await client.captureFeedback(fixture.fakeFeedback()); + + expect(fixture.recorder.discardedEvents.isEmpty, true); + }); + }); + + group('Spotlight', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test( + 'Spotlight enabled should not set transport to SpotlightHttpTransport on iOS', + () async { + fixture.options.platform = MockPlatform.iOS(); + fixture.options.spotlight = Spotlight(enabled: true); + fixture.getSut(); + + expect(fixture.options.transport is SpotlightHttpTransport, isFalse); + }); + + test( + 'Spotlight enabled should not set transport to SpotlightHttpTransport on macOS', + () async { + fixture.options.platform = MockPlatform.macOS(); + fixture.options.spotlight = Spotlight(enabled: true); + fixture.getSut(); + + expect(fixture.options.transport is SpotlightHttpTransport, isFalse); + }); + + test( + 'Spotlight enabled should not set transport to SpotlightHttpTransport on Android', + () async { + fixture.options.platform = MockPlatform.android(); + fixture.options.spotlight = Spotlight(enabled: true); + fixture.getSut(); + + expect(fixture.options.transport is SpotlightHttpTransport, isFalse); + }); + + test( + 'Spotlight enabled should set transport to SpotlightHttpTransport on Web', + () async { + fixture.options.platform = MockPlatform(isWeb: true); + fixture.options.spotlight = Spotlight(enabled: true); + fixture.getSut(); + + expect(fixture.options.transport is SpotlightHttpTransport, isTrue); + }); + + test( + 'Spotlight enabled should set transport to SpotlightHttpTransport on Linux', + () async { + fixture.options.platform = MockPlatform.linux(); + fixture.options.spotlight = Spotlight(enabled: true); + fixture.getSut(); + + expect(fixture.options.transport is SpotlightHttpTransport, isTrue); + }); + + test( + 'Spotlight enabled should set transport to SpotlightHttpTransport on Windows', + () async { + fixture.options.platform = MockPlatform.windows(); + fixture.options.spotlight = Spotlight(enabled: true); + fixture.getSut(); + + expect(fixture.options.transport is SpotlightHttpTransport, isTrue); + }); + }); + + group('trace context', () { + late Fixture fixture; + late SentryEvent fakeEvent; + + setUp(() { + fixture = Fixture(); + fakeEvent = getFakeEvent(); + }); + + test('captureEvent adds trace context', () async { + final client = fixture.getSut(); + + final scope = Scope(fixture.options); + scope.replayId = SentryId.newId(); + scope.span = + SentrySpan(fixture.tracer, fixture.tracer.context, MockHub()); + + await client.captureEvent(fakeEvent, scope: scope); + + final envelope = fixture.transport.envelopes.first; + expect(envelope.header.traceContext, isNotNull); + expect(envelope.header.traceContext?.replayId, scope.replayId); + }); + + test('captureTransaction adds trace context', () async { + final client = fixture.getSut(); + + final tr = SentryTransaction(fixture.tracer); + + final context = SentryTraceContextHeader.fromJson({ + 'trace_id': '${tr.eventId}', + 'public_key': '123', + 'replay_id': '456', + }); + + await client.captureTransaction(tr, traceContext: context); + + final envelope = fixture.transport.envelopes.first; + expect(envelope.header.traceContext, isNotNull); + expect(envelope.header.traceContext?.replayId, SentryId.fromId('456')); + }); + + test('captureFeedback adds trace context', () async { + final client = fixture.getSut(); + + final scope = Scope(fixture.options); + scope.span = + SentrySpan(fixture.tracer, fixture.tracer.context, MockHub()); + + await client.captureFeedback(fixture.fakeFeedback(), scope: scope); + + final envelope = fixture.transport.envelopes.first; + expect(envelope.header.traceContext, isNotNull); + }); + }); + + group('Hint', () { + late Fixture fixture; + late SentryEvent fakeEvent; + + setUp(() { + fixture = Fixture(); + fakeEvent = getFakeEvent(); + }); + + test('captureEvent adds attachments from hint', () async { + final attachment = SentryAttachment.fromIntList([], "fixture-fileName"); + final hint = Hint.withAttachment(attachment); + + final sut = fixture.getSut(); + await sut.captureEvent(fakeEvent, hint: hint); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final attachmentItem = IterableUtils.firstWhereOrNull( + capturedEnvelope.items, + (SentryEnvelopeItem e) => e.header.type == SentryItemType.attachment, + ); + expect(attachmentItem?.header.attachmentType, + SentryAttachment.typeAttachmentDefault); + }); + + test('captureFeedback adds attachments from hint', () async { + final attachment = SentryAttachment.fromIntList([], "fixture-fileName"); + final hint = Hint.withAttachment(attachment); + + final sut = fixture.getSut(); + final fakeFeedback = fixture.fakeFeedback(); + await sut.captureFeedback(fakeFeedback, hint: hint); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final attachmentItem = IterableUtils.firstWhereOrNull( + capturedEnvelope.items, + (SentryEnvelopeItem e) => e.header.type == SentryItemType.attachment, + ); + expect(attachmentItem?.header.attachmentType, + SentryAttachment.typeAttachmentDefault); + }); + + test('captureTransaction hint passed to beforeSendTransaction', () async { + final sut = fixture.getSut(); + + final hint = Hint(); + final transaction = SentryTransaction(fixture.tracer); + + fixture.options.beforeSendTransaction = (bsTransaction, bsHint) async { + expect(hint, bsHint); + return bsTransaction; + }; + + await sut.captureTransaction(transaction, hint: hint); + }); + + test('captureTransaction hint passed to event processors', () async { + final hint = Hint(); + + final eventProcessor = FunctionEventProcessor((event, epHint) { + expect(epHint, hint); + return event; + }); + final sut = fixture.getSut(eventProcessor: eventProcessor); + + final transaction = SentryTransaction(fixture.tracer); + await sut.captureTransaction(transaction, hint: hint); + }); + + test('captureEvent adds screenshot from hint', () async { + final client = fixture.getSut(); + final screenshot = + SentryAttachment.fromScreenshotData(Uint8List.fromList([0, 0, 0, 0])); + final hint = Hint.withScreenshot(screenshot); + + await client.captureEvent(fakeEvent, hint: hint); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final attachmentItem = capturedEnvelope.items.firstWhereOrNull( + (element) => element.header.type == SentryItemType.attachment); + expect(attachmentItem?.header.fileName, 'screenshot.png'); + }); + + test('captureFeedback adds screenshot from hint', () async { + final client = fixture.getSut(); + final screenshot = + SentryAttachment.fromScreenshotData(Uint8List.fromList([0, 0, 0, 0])); + final hint = Hint.withScreenshot(screenshot); + + final fakeFeedback = fixture.fakeFeedback(); + await client.captureFeedback(fakeFeedback, hint: hint); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final attachmentItem = capturedEnvelope.items.firstWhereOrNull( + (element) => element.header.type == SentryItemType.attachment); + expect(attachmentItem?.header.fileName, 'screenshot.png'); + }); + + test('captureEvent adds viewHierarchy from hint', () async { + final client = fixture.getSut(); + final view = SentryViewHierarchy('flutter'); + final attachment = SentryAttachment.fromViewHierarchy(view); + final hint = Hint.withViewHierarchy(attachment); + + await client.captureEvent(fakeEvent, hint: hint); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final attachmentItem = capturedEnvelope.items.firstWhereOrNull( + (element) => element.header.type == SentryItemType.attachment); + + expect(attachmentItem?.header.attachmentType, + SentryAttachment.typeViewHierarchy); + }); + + test('captureFeedback does not add viewHierarchy from hint', () async { + final client = fixture.getSut(); + final view = SentryViewHierarchy('flutter'); + final attachment = SentryAttachment.fromViewHierarchy(view); + final hint = Hint.withViewHierarchy(attachment); + + final fakeFeedback = fixture.fakeFeedback(); + await client.captureFeedback(fakeFeedback, hint: hint); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final attachmentItem = capturedEnvelope.items.firstWhereOrNull( + (element) => element.header.type == SentryItemType.attachment, + ); + expect(attachmentItem, isNull); + }); + + test( + 'null stack trace marked in hint & sentry frames removed from thread stackTrace', + () async { + final beforeSendCallback = (SentryEvent event, Hint hint) { + expect(hint.get(TypeCheckHint.currentStackTrace), isTrue); + return event; + }; + final client = fixture.getSut( + beforeSend: beforeSendCallback, attachStacktrace: true); + await client.captureEvent(fakeEvent); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + final sentryFramesCount = capturedEvent.threads?[0].stacktrace!.frames + .where((frame) => frame.package == 'sentry') + .length; + + expect(sentryFramesCount, 0); + }); + + test( + 'empty stack trace marked in hint & sentry frames removed from thread stackTrace', + () async { + final beforeSendCallback = (SentryEvent event, Hint hint) { + expect(hint.get(TypeCheckHint.currentStackTrace), isTrue); + return event; + }; + final client = fixture.getSut( + beforeSend: beforeSendCallback, attachStacktrace: true); + await client.captureEvent(fakeEvent, stackTrace: StackTrace.empty); + + final capturedEnvelope = (fixture.transport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + final sentryFramesCount = capturedEvent.threads?[0].stacktrace!.frames + .where((frame) => frame.package == 'sentry') + .length; + + expect(sentryFramesCount, 0); + }); + + test('non-null stack trace not marked in hint', () async { + final beforeSendCallback = (SentryEvent event, Hint hint) { + expect(hint.get(TypeCheckHint.currentStackTrace), isNull); + return event; + }; + final client = fixture.getSut( + beforeSend: beforeSendCallback, attachStacktrace: true); + await client.captureEvent(fakeEvent, stackTrace: StackTrace.current); + }); + }); +} + +Future eventFromEnvelope(SentryEnvelope envelope) async { + final data = await envelope.items.first.dataFactory(); + final utf8Data = utf8.decode(data); + final envelopeItemJson = jsonDecode(utf8Data); + return SentryEvent.fromJson(envelopeItemJson as Map); +} + +Future> transactionFromEnvelope( + SentryEnvelope envelope) async { + final data = await envelope.items.first.dataFactory(); + final utf8Data = utf8.decode(data); + final envelopeItemJson = jsonDecode(utf8Data); + return envelopeItemJson as Map; +} + +SentryEvent? beforeSendCallbackDropEvent( + SentryEvent event, + Hint hint, +) => + null; + +SentryTransaction? beforeSendFeedbackCallbackDropEvent( + SentryEvent feedbackEvent, + Hint hint, +) => + null; + +Future asyncBeforeSendFeedbackCallbackDropEvent( + SentryEvent feedbackEvent, + Hint hint, +) async { + await Future.delayed(Duration(milliseconds: 200)); + return null; +} + +SentryTransaction? beforeSendTransactionCallbackDropEvent( + SentryTransaction event, + Hint hint, +) => + null; + +Future asyncBeforeSendCallbackDropEvent( + SentryEvent event, + Hint hint, +) async { + await Future.delayed(Duration(milliseconds: 200)); + return null; +} + +Future asyncBeforeSendTransactionCallbackDropEvent( + SentryEvent event, + Hint hint, +) async { + await Future.delayed(Duration(milliseconds: 200)); + return null; +} + +SentryEvent? beforeSendFeedbackCallback(SentryEvent event, Hint hint) { + event.tags = {'theme': 'material'}; + return event; +} + +SentryEvent? beforeSendCallback(SentryEvent event, Hint hint) { + return event + ..tags!.addAll({'theme': 'material'}) + // ignore: deprecated_member_use_from_same_package + ..extra!['host'] = '0.0.0.1' + ..modules!.addAll({'core': '1.0'}) + ..breadcrumbs!.add(Breadcrumb(message: 'processor crumb')) + ..fingerprint!.add('process') + ..sdk!.addIntegration('testIntegration') + ..sdk!.addPackage('test-pkg', '1.0'); +} + +SentryTransaction? beforeSendTransactionCallback( + SentryTransaction transaction, + Hint hint, +) { + return transaction + ..tags!.addAll({'theme': 'material'}) + // ignore: deprecated_member_use_from_same_package + ..extra!['host'] = '0.0.0.1' + ..sdk!.addIntegration('testIntegration') + ..sdk!.addPackage('test-pkg', '1.0') + ..breadcrumbs!.add(Breadcrumb(message: 'processor crumb')); +} + +class Fixture { + final recorder = MockClientReportRecorder(); + final transport = MockTransport(); + + final options = defaultTestOptions() + ..platform = MockPlatform.iOS() + ..groupExceptions = true; + + late SentryTransactionContext _context; + late SentryTracer tracer; + + SentryLevel? loggedLevel; + Object? loggedException; + + SentryClient getSut({ + bool sendDefaultPii = false, + bool attachStacktrace = true, + bool attachThreads = false, + double? sampleRate, + BeforeSendCallback? beforeSend, + BeforeSendTransactionCallback? beforeSendTransaction, + BeforeSendCallback? beforeSendFeedback, + EventProcessor? eventProcessor, + bool provideMockRecorder = true, + bool debug = false, + Transport? transport, + }) { + options.tracesSampleRate = 1.0; + options.sendDefaultPii = sendDefaultPii; + options.attachStacktrace = attachStacktrace; + options.attachThreads = attachThreads; + options.sampleRate = sampleRate; + options.beforeSend = beforeSend; + options.beforeSendTransaction = beforeSendTransaction; + options.beforeSendFeedback = beforeSendFeedback; + options.debug = debug; + options.log = mockLogger; + + if (eventProcessor != null) { + options.addEventProcessor(eventProcessor); + } + + // Internally also creates a SentryClient instance + final hub = Hub(options); + _context = SentryTransactionContext( + 'name', + 'op', + ); + tracer = SentryTracer(_context, hub); + + // Reset transport + options.transport = transport ?? this.transport; + + // Again create SentryClient instance + final client = SentryClient(options); + + if (provideMockRecorder) { + options.recorder = recorder; + } + return client; + } + + Future droppingBeforeSend(SentryEvent event, Hint hint) async { + return null; + } + + SentryTransaction fakeTransaction() { + return SentryTransaction( + tracer, + sdk: SdkVersion(name: 'sdk1', version: '1.0.0'), + breadcrumbs: [], + ); + } + + SentryEvent fakeFeedbackEvent() { + return SentryEvent( + type: 'feedback', + contexts: Contexts(feedback: fakeFeedback()), + level: SentryLevel.info, + ); + } + + SentryFeedback fakeFeedback() { + return SentryFeedback( + message: 'fixture-message', + contactEmail: 'fixture-contactEmail', + name: 'fixture-name', + replayId: 'fixture-replayId', + url: "https://fixture-url.com", + associatedEventId: SentryId.fromId('1d49af08b6e2c437f9052b1ecfd83dca'), + ); + } + + void mockLogger( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + loggedLevel = level; + loggedException = exception; + } +} + +class ExceptionWithCause { + ExceptionWithCause(this.cause, this.stackTrace); + + final dynamic cause; + final dynamic stackTrace; +} + +class ExceptionWithCauseExtractor + extends ExceptionCauseExtractor { + @override + ExceptionCause? cause(ExceptionWithCause error) { + return ExceptionCause(error.cause, error.stackTrace, source: "cause"); + } +} + +class ExceptionWithStackTrace { + ExceptionWithStackTrace(this.stackTrace); + + final StackTrace stackTrace; +} + +class ExceptionWithStackTraceExtractor + extends ExceptionStackTraceExtractor { + @override + StackTrace? stackTrace(ExceptionWithStackTrace error) { + return error.stackTrace; + } +} diff --git a/packages/dart/test/sentry_envelope_header_test.dart b/packages/dart/test/sentry_envelope_header_test.dart new file mode 100644 index 0000000000..cc10f97434 --- /dev/null +++ b/packages/dart/test/sentry_envelope_header_test.dart @@ -0,0 +1,44 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_envelope_header.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; + +void main() { + group('SentryEnvelopeHeader', () { + test('toJson empty', () { + final sut = SentryEnvelopeHeader(null, null); + final expected = {}; + expect(sut.toJson(), expected); + }); + + test('toJson', () async { + final eventId = SentryId.newId(); + final sdkVersion = SdkVersion( + name: 'fixture-sdkName', + version: 'fixture-version', + ); + final context = SentryTraceContextHeader.fromJson({ + 'trace_id': '${SentryId.newId()}', + 'public_key': '123', + }); + final timestamp = DateTime.utc(2019); + final sut = SentryEnvelopeHeader( + eventId, + sdkVersion, + dsn: fakeDsn, + traceContext: context, + sentAt: timestamp, + ); + final expextedSkd = sdkVersion.toJson(); + final expected = { + 'event_id': eventId.toString(), + 'sdk': expextedSkd, + 'trace': context.toJson(), + 'dsn': fakeDsn, + 'sent_at': formatDateAsIso8601WithMillisPrecision(timestamp), + }; + expect(sut.toJson(), expected); + }); + }); +} diff --git a/packages/dart/test/sentry_envelope_item_header_test.dart b/packages/dart/test/sentry_envelope_item_header_test.dart new file mode 100644 index 0000000000..b948568ed9 --- /dev/null +++ b/packages/dart/test/sentry_envelope_item_header_test.dart @@ -0,0 +1,19 @@ +import 'package:sentry/src/sentry_envelope_item_header.dart'; +import 'package:sentry/src/sentry_item_type.dart'; +import 'package:test/test.dart'; + +void main() { + group('SentryEnvelopeItemHeader', () { + test('serialize', () async { + final sut = SentryEnvelopeItemHeader(SentryItemType.event, + itemCount: 3, contentType: 'application/json'); + final expected = { + 'item_count': 3, + 'content_type': 'application/json', + 'type': 'event', + 'length': 3 + }; + expect(await sut.toJson(3), expected); + }); + }); +} diff --git a/packages/dart/test/sentry_envelope_item_test.dart b/packages/dart/test/sentry_envelope_item_test.dart new file mode 100644 index 0000000000..741773a66a --- /dev/null +++ b/packages/dart/test/sentry_envelope_item_test.dart @@ -0,0 +1,136 @@ +import 'dart:convert'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/client_reports/client_report.dart'; +import 'package:sentry/src/client_reports/discard_reason.dart'; +import 'package:sentry/src/client_reports/discarded_event.dart'; +import 'package:sentry/src/sentry_item_type.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:sentry/src/transport/data_category.dart'; +import 'package:test/test.dart'; + +import 'mocks/mock_hub.dart'; + +void main() { + group('SentryEnvelopeItem', () { + test('fromEvent', () async { + final eventId = SentryId.newId(); + final sentryEvent = SentryEvent(eventId: eventId); + final sut = SentryEnvelopeItem.fromEvent(sentryEvent); + + final expectedData = utf8.encode(jsonEncode( + sentryEvent.toJson(), + toEncodable: jsonSerializationFallback, + )); + final actualData = await sut.dataFactory(); + + expect(sut.header.contentType, 'application/json'); + expect(sut.header.type, SentryItemType.event); + expect(actualData, expectedData); + }); + + test('fromEvent feedback', () async { + final feedback = SentryFeedback( + message: 'fixture-message', + ); + final feedbackEvent = SentryEvent( + type: 'feedback', + contexts: Contexts(feedback: feedback), + level: SentryLevel.info, + ); + final sut = SentryEnvelopeItem.fromEvent(feedbackEvent); + + final expectedData = utf8.encode(jsonEncode( + feedbackEvent.toJson(), + toEncodable: jsonSerializationFallback, + )); + final actualData = await sut.dataFactory(); + + expect(sut.header.contentType, 'application/json'); + expect(sut.header.type, 'feedback'); + expect(actualData, expectedData); + }); + + test('fromTransaction', () async { + final context = SentryTransactionContext( + 'name', + 'op', + ); + final tracer = SentryTracer(context, MockHub()); + final tr = SentryTransaction(tracer); + tr.contexts.device = SentryDevice( + orientation: SentryOrientation.landscape, + ); + + final sut = SentryEnvelopeItem.fromTransaction(tr); + + final expectedData = utf8.encode(jsonEncode( + tr.toJson(), + toEncodable: jsonSerializationFallback, + )); + final actualData = await sut.dataFactory(); + + expect(sut.header.contentType, 'application/json'); + expect(sut.header.type, SentryItemType.transaction); + expect(actualData, expectedData); + }); + + test('fromClientReport', () async { + final timestamp = DateTime(0); + final discardedEvents = [ + DiscardedEvent(DiscardReason.rateLimitBackoff, DataCategory.error, 1) + ]; + + final cr = ClientReport(timestamp, discardedEvents); + + final sut = SentryEnvelopeItem.fromClientReport(cr); + + final expectedData = utf8.encode(jsonEncode( + cr.toJson(), + toEncodable: jsonSerializationFallback, + )); + final actualData = await sut.dataFactory(); + + expect(sut.header.contentType, 'application/json'); + expect(sut.header.type, SentryItemType.clientReport); + expect(actualData, expectedData); + }); + + test('fromLog', () async { + final logs = [ + SentryLog( + timestamp: DateTime.now(), + traceId: SentryId.newId(), + level: SentryLogLevel.info, + body: 'test', + attributes: { + 'test': SentryLogAttribute.string('test'), + }, + ), + SentryLog( + timestamp: DateTime.now(), + traceId: SentryId.newId(), + level: SentryLogLevel.info, + body: 'test2', + attributes: { + 'test2': SentryLogAttribute.int(9001), + }, + ), + ]; + + final sut = SentryEnvelopeItem.fromLogs(logs); + + final expectedData = utf8.encode(jsonEncode( + { + 'items': logs.map((e) => e.toJson()).toList(), + }, + toEncodable: jsonSerializationFallback, + )); + final actualData = await sut.dataFactory(); + + expect(sut.header.contentType, 'application/vnd.sentry.items.log+json'); + expect(sut.header.type, SentryItemType.log); + expect(actualData, expectedData); + }); + }); +} diff --git a/packages/dart/test/sentry_envelope_test.dart b/packages/dart/test/sentry_envelope_test.dart new file mode 100644 index 0000000000..9a21339aaa --- /dev/null +++ b/packages/dart/test/sentry_envelope_test.dart @@ -0,0 +1,276 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_envelope_header.dart'; +import 'package:sentry/src/sentry_envelope_item_header.dart'; +import 'package:sentry/src/sentry_item_type.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; +import 'mocks/mock_hub.dart'; +import 'test_utils.dart'; + +void main() { + group('SentryEnvelope', () { + Future serializedItem(SentryEnvelopeItem item) async { + final expectedItemData = await item.dataFactory(); + final expectedItemHeader = utf8JsonEncoder + .convert(await item.header.toJson(expectedItemData.length)); + final newLine = utf8.encode('\n'); + final expectedItem = [ + ...expectedItemHeader, + ...newLine, + ...expectedItemData + ]; + return utf8.decode(expectedItem); + } + + test('serialize', () async { + final eventId = SentryId.newId(); + + final itemHeader = SentryEnvelopeItemHeader(SentryItemType.event, + contentType: 'application/json'); + + final dataFactory = () async { + return utf8.encode('{fixture}'); + }; + + final item = SentryEnvelopeItem(itemHeader, dataFactory); + + final context = SentryTraceContextHeader.fromJson({ + 'trace_id': '${SentryId.newId()}', + 'public_key': '123', + }); + final header = SentryEnvelopeHeader( + eventId, + null, + traceContext: context, + ); + final sut = SentryEnvelope(header, [item, item]); + + final expectedHeaderJson = header.toJson(); + final expectedHeaderJsonSerialized = jsonEncode( + expectedHeaderJson, + toEncodable: jsonSerializationFallback, + ); + + final expectedItemSerialized = await serializedItem(item); + + final expected = utf8.encode( + '$expectedHeaderJsonSerialized\n$expectedItemSerialized\n$expectedItemSerialized'); + + final envelopeData = []; + await sut + .envelopeStream(defaultTestOptions()) + .forEach(envelopeData.addAll); + expect(envelopeData, expected); + }); + + test('fromEvent', () async { + final eventId = SentryId.newId(); + final sentryEvent = SentryEvent(eventId: eventId); + final sdkVersion = + SdkVersion(name: 'fixture-name', version: 'fixture-version'); + final context = SentryTraceContextHeader.fromJson({ + 'trace_id': '${SentryId.newId()}', + 'public_key': '123', + }); + final sut = SentryEnvelope.fromEvent( + sentryEvent, + sdkVersion, + dsn: fakeDsn, + traceContext: context, + ); + + final expectedEnvelopeItem = SentryEnvelopeItem.fromEvent(sentryEvent); + + expect(sut.header.eventId, eventId); + expect(sut.header.sdkVersion, sdkVersion); + expect(sut.header.traceContext, context); + expect(sut.header.dsn, fakeDsn); + expect(sut.items[0].header.contentType, + expectedEnvelopeItem.header.contentType); + expect(sut.items[0].header.type, expectedEnvelopeItem.header.type); + + final actualItem = await sut.items[0].dataFactory(); + final expectedItem = await expectedEnvelopeItem.dataFactory(); + expect(actualItem, expectedItem); + }); + + test('fromTransaction', () async { + final context = SentryTransactionContext( + 'name', + 'op', + ); + final tracer = SentryTracer(context, MockHub()); + final tr = SentryTransaction(tracer); + + final sdkVersion = + SdkVersion(name: 'fixture-name', version: 'fixture-version'); + final traceContext = SentryTraceContextHeader.fromJson({ + 'trace_id': '${SentryId.newId()}', + 'public_key': '123', + }); + final sut = SentryEnvelope.fromTransaction( + tr, + sdkVersion, + dsn: fakeDsn, + traceContext: traceContext, + ); + + final expectedEnvelopeItem = SentryEnvelopeItem.fromTransaction(tr); + + expect(sut.header.eventId, tr.eventId); + expect(sut.header.sdkVersion, sdkVersion); + expect(sut.header.traceContext, traceContext); + expect(sut.header.dsn, fakeDsn); + expect(sut.items[0].header.contentType, + expectedEnvelopeItem.header.contentType); + expect(sut.items[0].header.type, expectedEnvelopeItem.header.type); + + final actualItem = await sut.items[0].dataFactory(); + final expectedItem = await expectedEnvelopeItem.dataFactory(); + expect(actualItem, expectedItem); + }); + + test('fromLogs', () async { + final logs = [ + SentryLog( + timestamp: DateTime.now(), + traceId: SentryId.newId(), + level: SentryLogLevel.info, + body: 'test', + attributes: { + 'test': SentryLogAttribute.string('test'), + }, + ), + SentryLog( + timestamp: DateTime.now(), + traceId: SentryId.newId(), + level: SentryLogLevel.info, + body: 'test2', + attributes: { + 'test2': SentryLogAttribute.int(9001), + }, + ), + ]; + + final sdkVersion = SdkVersion( + name: 'fixture-name', + version: 'fixture-version', + ); + + final sut = SentryEnvelope.fromLogs(logs, sdkVersion); + + expect(sut.header.sdkVersion, sdkVersion); + + final expectedItem = SentryEnvelopeItem.fromLogs(logs); + final expectedItemData = await expectedItem.dataFactory(); + final actualItemData = await sut.items[0].dataFactory(); + + expect(actualItemData, expectedItemData); + }); + + test('max attachment size', () async { + final attachment = SentryAttachment.fromLoader( + loader: () => Uint8List.fromList([1, 2, 3, 4]), + filename: 'test.txt', + ); + + final eventId = SentryId.newId(); + final sentryEvent = SentryEvent(eventId: eventId); + final sdkVersion = + SdkVersion(name: 'fixture-name', version: 'fixture-version'); + + final sut = SentryEnvelope.fromEvent( + sentryEvent, + sdkVersion, + dsn: fakeDsn, + attachments: [attachment], + ); + + final expectedEnvelopeItem = SentryEnvelope.fromEvent( + sentryEvent, + sdkVersion, + dsn: fakeDsn, + ); + + final sutEnvelopeData = []; + await sut + .envelopeStream(defaultTestOptions()..maxAttachmentSize = 1) + .forEach(sutEnvelopeData.addAll); + + final envelopeData = []; + await expectedEnvelopeItem + .envelopeStream(defaultTestOptions()) + .forEach(envelopeData.addAll); + + expect(sutEnvelopeData, envelopeData); + }); + + test('ignore throwing envelope items', () async { + final eventId = SentryId.newId(); + + final itemHeader = SentryEnvelopeItemHeader(SentryItemType.event, + contentType: 'application/json'); + final dataFactory = () async { + return utf8.encode('{fixture}'); + }; + final dataFactoryThrowing = () async { + throw Exception('Exception in data factory.'); + }; + + final item = SentryEnvelopeItem(itemHeader, dataFactory); + final throwingItem = SentryEnvelopeItem(itemHeader, dataFactoryThrowing); + + final context = SentryTraceContextHeader.fromJson({ + 'trace_id': '${SentryId.newId()}', + 'public_key': '123', + }); + final header = SentryEnvelopeHeader( + eventId, + null, + traceContext: context, + ); + final sut = SentryEnvelope(header, [item, throwingItem]); + + final expectedHeaderJson = header.toJson(); + final expectedHeaderJsonSerialized = jsonEncode( + expectedHeaderJson, + toEncodable: jsonSerializationFallback, + ); + + final expectedItemSerialized = await serializedItem(item); + + final expected = + utf8.encode('$expectedHeaderJsonSerialized\n$expectedItemSerialized'); + + final options = defaultTestOptions(); + options.automatedTestMode = false; // Test if throwing item is ignored. + final envelopeData = []; + await sut.envelopeStream(options).forEach(envelopeData.addAll); + expect(envelopeData, expected); + }); + + // This test passes if no exceptions are thrown, thus no asserts. + // This is a test for https://github.com/getsentry/sentry-dart/issues/523 + test('serialize with non-serializable class', () async { + // ignore: deprecated_member_use_from_same_package + final event = SentryEvent(extra: {'non-encodable': NonEncodable()}); + final sut = SentryEnvelope.fromEvent( + event, + SdkVersion( + name: 'test', + version: '1', + ), + dsn: fakeDsn, + ); + + final _ = sut.envelopeStream(defaultTestOptions()).map((e) => e); + }); + }); +} + +class NonEncodable {} diff --git a/packages/dart/test/sentry_envelope_vm_test.dart b/packages/dart/test/sentry_envelope_vm_test.dart new file mode 100644 index 0000000000..96da255234 --- /dev/null +++ b/packages/dart/test/sentry_envelope_vm_test.dart @@ -0,0 +1,78 @@ +@TestOn('vm') +library; + +import 'dart:convert'; +import 'dart:io'; + +import 'package:sentry/sentry_io.dart'; +import 'package:sentry/src/sentry_envelope_header.dart'; +import 'package:sentry/src/sentry_envelope_item_header.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; +import 'test_utils.dart'; + +void main() { + group('SentryEnvelopeItem', () { + test('item with binary payload', () async { + // Attachment + + // length == 3535 + final dataFactory = () async { + final file = File('test_resources/sentry.png'); + final bytes = await file.readAsBytes(); + return bytes; + }; + final attachmentHeader = SentryEnvelopeItemHeader('attachment', + contentType: 'image/png', fileName: 'sentry.png'); + final attachmentItem = SentryEnvelopeItem(attachmentHeader, dataFactory); + + // Envelope + + final eventId = SentryId.fromId('3b382f22ee67491f80f7dee18016a7b1'); + final sdkVersion = SdkVersion(name: 'test', version: 'version'); + final header = SentryEnvelopeHeader(eventId, sdkVersion); + final envelope = SentryEnvelope(header, [attachmentItem]); + + final envelopeData = []; + await envelope + .envelopeStream(defaultTestOptions()) + .forEach(envelopeData.addAll); + + final expectedEnvelopeFile = + File('test_resources/envelope-with-image.envelope'); + final expectedEnvelopeData = await expectedEnvelopeFile.readAsBytes(); + + expect(expectedEnvelopeData, envelopeData); + }); + + test('skips attachment if path is invalid', () async { + final event = SentryEvent( + eventId: SentryId.empty(), + timestamp: DateTime.utc(1970, 1, 1), + ); + final sdkVersion = SdkVersion(name: '', version: ''); + final attachment = + IoSentryAttachment.fromPath('this_path_does_not_exist.txt'); + final envelope = SentryEnvelope.fromEvent( + event, + sdkVersion, + dsn: fakeDsn, + attachments: [attachment], + ); + + final options = SentryOptions(dsn: testDsn) + ..automatedTestMode = + false; // We want to skip throwing envelope items in this test. + + final data = (await envelope.envelopeStream(options).toList()) + .reduce((a, b) => a + b); + + final file = File('test_resources/envelope-no-attachment.envelope'); + final jsonStr = await file.readAsString(); + final dataStr = utf8.decode(data); + + expect(dataStr, jsonStr); + }); + }); +} diff --git a/packages/dart/test/sentry_event_test.dart b/packages/dart/test/sentry_event_test.dart new file mode 100644 index 0000000000..4a88a7aecb --- /dev/null +++ b/packages/dart/test/sentry_event_test.dart @@ -0,0 +1,500 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/platform/mock_platform.dart'; +import 'package:sentry/src/version.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; + +void main() { + group('deserialize', () { + final sentryId = SentryId.empty(); + final timestamp = DateTime.fromMillisecondsSinceEpoch(0); + final sentryEventJson = { + 'event_id': sentryId.toString(), + 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), + 'platform': 'platform', + 'logger': 'logger', + 'server_name': 'serverName', + 'release': 'release', + 'dist': 'dist', + 'environment': 'environment', + 'modules': {'key': 'value'}, + 'message': {'formatted': 'formatted'}, + 'transaction': 'transaction', + 'exception': { + 'values': [ + {'type': 'type', 'value': 'value'} + ] + }, + 'threads': { + 'values': [ + {'id': 0, 'crashed': true} + ] + }, + 'level': 'debug', + 'culprit': 'culprit', + 'tags': {'key': 'value'}, + 'extra': {'key': 'value'}, + 'contexts': { + 'device': {'name': 'name'} + }, + 'user': { + 'id': 'id', + 'username': 'username', + 'ip_address': '192.168.0.0.1' + }, + 'fingerprint': ['fingerprint'], + 'breadcrumbs': [ + { + 'message': 'message', + 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), + 'level': 'info' + } + ], + 'sdk': {'name': 'name', 'version': 'version'}, + 'request': {'url': 'url'}, + 'debug_meta': { + 'sdk_info': {'sdk_name': 'sdkName'} + }, + 'type': 'type', + }; + sentryEventJson.addAll(testUnknown); + + final emptyFieldsSentryEventJson = { + 'event_id': sentryId.toString(), + 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), + 'contexts': { + 'device': {'name': 'name'} + }, + }; + + test('fromJson', () { + final sentryEvent = SentryEvent.fromJson(sentryEventJson); + final json = sentryEvent.toJson(); + + expect( + DeepCollectionEquality().equals(sentryEventJson, json), + true, + ); + }); + + test('should not deserialize null or empty fields', () { + final sentryEvent = SentryEvent.fromJson(emptyFieldsSentryEventJson); + + expect(sentryEvent.platform, isNull); + expect(sentryEvent.logger, isNull); + expect(sentryEvent.serverName, isNull); + expect(sentryEvent.release, isNull); + expect(sentryEvent.dist, isNull); + expect(sentryEvent.environment, isNull); + expect(sentryEvent.modules, isNull); + expect(sentryEvent.message, isNull); + expect(sentryEvent.threads?.first.stacktrace, isNull); + expect(sentryEvent.exceptions?.first, isNull); + expect(sentryEvent.transaction, isNull); + expect(sentryEvent.level, isNull); + expect(sentryEvent.culprit, isNull); + expect(sentryEvent.tags, isNull); + // ignore: deprecated_member_use_from_same_package + expect(sentryEvent.extra, isNull); + expect(sentryEvent.breadcrumbs, isNull); + expect(sentryEvent.user, isNull); + expect(sentryEvent.fingerprint, isNull); + expect(sentryEvent.sdk, isNull); + expect(sentryEvent.request, isNull); + expect(sentryEvent.debugMeta, isNull); + expect(sentryEvent.type, isNull); + expect(sentryEvent.unknown, isNull); + }); + }); + + group(SentryEvent, () { + test('$Breadcrumb serializes', () { + expect( + Breadcrumb( + message: 'example log', + timestamp: DateTime.utc(2019), + level: SentryLevel.debug, + category: 'test', + ).toJson(), + { + 'timestamp': '2019-01-01T00:00:00.000Z', + 'message': 'example log', + 'category': 'test', + 'level': 'debug', + }, + ); + }); + test('$SdkVersion serializes', () { + final platform = MockPlatform(); + + final event = SentryEvent( + eventId: SentryId.empty(), + timestamp: DateTime.utc(2019), + platform: sdkPlatform(platform.isWeb), + sdk: SdkVersion( + name: 'sentry.dart.flutter', + version: '4.3.2', + integrations: ['integration'], + packages: [ + SentryPackage('npm:@sentry/javascript', '1.3.4'), + ], + ), + ); + expect(event.toJson(), { + 'platform': platform.isWeb ? 'javascript' : 'other', + 'event_id': '00000000000000000000000000000000', + 'timestamp': '2019-01-01T00:00:00.000Z', + 'sdk': { + 'name': 'sentry.dart.flutter', + 'version': '4.3.2', + 'packages': [ + {'name': 'npm:@sentry/javascript', 'version': '1.3.4'} + ], + 'integrations': ['integration'], + }, + }); + }); + test('serializes to JSON', () { + final platform = MockPlatform(); + + final timestamp = DateTime.utc(2019); + final user = SentryUser( + id: 'user_id', + username: 'username', + email: 'email@email.com', + ipAddress: '127.0.0.1', + data: const {'foo': 'bar'}, + ); + + final breadcrumbs = [ + Breadcrumb( + message: 'test log', + timestamp: timestamp, + level: SentryLevel.debug, + category: 'test', + ), + ]; + + final request = SentryRequest( + url: 'https://api.com/users', + method: 'GET', + headers: const {'authorization': '123456'}, + ); + + expect( + SentryEvent( + eventId: SentryId.empty(), + timestamp: timestamp, + platform: sdkPlatform(platform.isWeb), + message: SentryMessage( + 'test-message 1 2', + template: 'test-message %d %d', + params: ['1', '2'], + ), + transaction: '/test/1', + level: SentryLevel.debug, + culprit: 'Professor Moriarty', + tags: const { + 'a': 'b', + 'c': 'd', + }, + // ignore: deprecated_member_use_from_same_package + extra: const { + 'e': 'f', + 'g': 2, + }, + fingerprint: const [ + SentryEvent.defaultFingerprint, + 'foo' + ], + user: user, + breadcrumbs: breadcrumbs, + request: request, + debugMeta: DebugMeta( + sdk: SdkInfo( + sdkName: 'sentry.dart', + versionMajor: 4, + versionMinor: 1, + versionPatchlevel: 2, + ), + images: [ + DebugImage( + type: 'macho', + debugId: '84a04d24-0e60-3810-a8c0-90a65e2df61a', + debugFile: 'libDiagnosticMessagesClient.dylib', + codeFile: '/usr/lib/libDiagnosticMessagesClient.dylib', + imageAddr: '0x7fffe668e000', + imageSize: 8192, + arch: 'x86_64', + codeId: '123', + ) + ], + ), + type: 'type', + unknown: testUnknown) + .toJson(), + { + 'platform': platform.isWeb ? 'javascript' : 'other', + 'event_id': '00000000000000000000000000000000', + 'timestamp': '2019-01-01T00:00:00.000Z', + 'message': { + 'formatted': 'test-message 1 2', + 'message': 'test-message %d %d', + 'params': ['1', '2'] + }, + 'transaction': '/test/1', + 'level': 'debug', + 'culprit': 'Professor Moriarty', + 'tags': {'a': 'b', 'c': 'd'}, + 'extra': {'e': 'f', 'g': 2}, + 'fingerprint': ['{{ default }}', 'foo'], + 'user': { + 'id': 'user_id', + 'username': 'username', + 'email': 'email@email.com', + 'ip_address': '127.0.0.1', + 'data': {'foo': 'bar'} + }, + 'breadcrumbs': { + { + 'timestamp': '2019-01-01T00:00:00.000Z', + 'message': 'test log', + 'category': 'test', + 'level': 'debug', + }, + }, + 'request': { + 'url': request.url, + 'method': request.method, + 'headers': {'authorization': '123456'} + }, + 'debug_meta': { + 'sdk_info': { + 'sdk_name': 'sentry.dart', + 'version_major': 4, + 'version_minor': 1, + 'version_patchlevel': 2 + }, + 'images': [ + { + 'type': 'macho', + 'debug_id': '84a04d24-0e60-3810-a8c0-90a65e2df61a', + 'debug_file': 'libDiagnosticMessagesClient.dylib', + 'code_file': '/usr/lib/libDiagnosticMessagesClient.dylib', + 'image_addr': '0x7fffe668e000', + 'image_size': 8192, + 'arch': 'x86_64', + 'code_id': '123', + }, + ] + }, + 'type': 'type', + }..addAll(testUnknown), + ); + }); + + test('should not serialize throwable', () { + final error = StateError('test-error'); + + final serialized = SentryEvent(throwable: error).toJson(); + expect(serialized['throwable'], null); + expect(serialized['stacktrace'], null); + expect(serialized['exception'], null); + }); + + test('should serialize $SentryThread when no $SentryException present', () { + final serialized = SentryEvent(threads: [ + SentryThread( + id: 0, + crashed: true, + current: true, + name: 'Isolate', + ) + ]).toJson(); + expect(serialized['threads']['values'], isNotNull); + }); + + test('should serialize $SentryThread when id matches exception id', () { + final serialized = SentryEvent( + exceptions: [ + SentryException( + type: 'foo', + value: 'bar', + threadId: 0, + ) + ], + threads: [ + SentryThread( + id: 0, + crashed: true, + current: true, + name: 'Isolate', + ) + ], + ).toJson(); + expect(serialized['threads']?['values'], isNotEmpty); + }); + + test( + 'should not serialize event.threads.stacktrace ' + 'if event.exception is set', () { + // https://develop.sentry.dev/sdk/event-payloads/stacktrace/ + final stacktrace = + SentryStackTrace(frames: [SentryStackFrame(function: 'main')]); + final serialized = SentryEvent( + exceptions: [ + SentryException( + value: 'Bad state', + type: 'StateError', + threadId: 0, + stackTrace: stacktrace, + ) + ], + threads: [ + SentryThread( + crashed: true, + current: true, + id: 0, + name: 'Current isolate', + stacktrace: stacktrace, + ) + ], + ).toJson(); + + expect(serialized['threads']?['values']?.first['stacktrace'], isNull); + expect(serialized['threads']?['values']?.first['crashed'], true); + expect(serialized['threads']?['values']?.first['current'], true); + expect(serialized['threads']?['values']?.first['id'], 0); + expect( + serialized['threads']?['values']?.first['name'], + 'Current isolate', + ); + }); + + test( + 'should serialize event.threads.stacktrace ' + 'if event.exception.threadId does not match', () { + // https://develop.sentry.dev/sdk/event-payloads/stacktrace/ + final stacktrace = + SentryStackTrace(frames: [SentryStackFrame(function: 'main')]); + final serialized = SentryEvent( + exceptions: [ + SentryException( + value: 'Bad state', + type: 'StateError', + threadId: 1, + stackTrace: stacktrace, + ) + ], + threads: [ + SentryThread( + crashed: true, + current: true, + id: 0, + name: 'Current isolate', + stacktrace: stacktrace, + ) + ], + ).toJson(); + expect(serialized['threads']?['values'], isNotEmpty); + }); + + test('serializes to JSON with sentryException', () { + SentryException? sentryException; + try { + throw StateError('an error'); + } catch (err) { + sentryException = SentryException( + type: '${err.runtimeType}', + value: '$err', + mechanism: Mechanism( + type: 'mech-type', + description: 'a description', + helpLink: 'https://help.com', + synthetic: false, + handled: true, + meta: {}, + data: {}, + ), + ); + } + + final serialized = SentryEvent(exceptions: [sentryException]).toJson(); + + expect(serialized['exception']['values'].first['type'], 'StateError'); + expect( + serialized['exception']['values'].first['value'], + 'Bad state: an error', + ); + expect( + serialized['exception']['values'].first['mechanism'], + { + 'type': 'mech-type', + 'description': 'a description', + 'help_link': 'https://help.com', + 'synthetic': false, + 'handled': true, + }, + ); + }); + + test('should not serialize null or empty fields', () { + final event = SentryEvent( + message: null, + modules: {}, + exceptions: [SentryException(type: null, value: null)], + threads: [SentryThread(stacktrace: SentryStackTrace(frames: []))], + tags: {}, + // ignore: deprecated_member_use_from_same_package + extra: {}, + contexts: Contexts(), + fingerprint: [], + breadcrumbs: [Breadcrumb()], + request: SentryRequest(), + debugMeta: DebugMeta(images: []), + type: null, + ); + final eventMap = event.toJson(); + + expect(eventMap['message'], isNull); + expect(eventMap['modules'], isNull); + expect(eventMap['exception'], isNull); + expect(eventMap['stacktrace'], isNull); + expect(eventMap['tags'], isNull); + expect(eventMap['extra'], isNull); + expect(eventMap['contexts'], isNull); + expect(eventMap['fingerprint'], isNull); + expect(eventMap['request'], isNull); + expect(eventMap['debug_meta'], isNull); + expect(eventMap['type'], isNull); + }); + + test( + 'throwable and throwableMechanism should return the error if no mechanism', + () { + final error = StateError('test-error'); + final event = SentryEvent(throwable: error); + + expect(event.throwable, error); + expect(event.throwableMechanism, error); + }); + + test( + 'throwableMechanism getter should return the ThrowableMechanism if theres a mechanism', + () { + final error = StateError('test-error'); + final mechanism = Mechanism(type: 'FlutterError', handled: true); + final throwableMechanism = ThrowableMechanism(mechanism, error); + final event = SentryEvent(throwable: throwableMechanism); + + expect(event.throwable, error); + expect(event.throwableMechanism, throwableMechanism); + }); + }); +} diff --git a/packages/dart/test/sentry_exception_factory_test.dart b/packages/dart/test/sentry_exception_factory_test.dart new file mode 100644 index 0000000000..ad6df2a724 --- /dev/null +++ b/packages/dart/test/sentry_exception_factory_test.dart @@ -0,0 +1,355 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_exception_factory.dart'; +import 'package:test/test.dart'; + +import 'test_utils.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('getSentryException with frames', () { + SentryException sentryException; + try { + throw StateError('a state error'); + } catch (err, stacktrace) { + sentryException = fixture.getSut().getSentryException( + err, + stackTrace: stacktrace, + ); + } + + expect(sentryException.type, 'StateError'); + expect(sentryException.stackTrace!.frames, isNotEmpty); + }); + + test('getSentryException without frames', () { + SentryException sentryException; + try { + throw StateError('a state error'); + } catch (err, _) { + sentryException = fixture.getSut().getSentryException( + err, + stackTrace: '', + ); + } + + expect(sentryException.type, 'StateError'); + expect(sentryException.stackTrace, isNull); + }); + + test('getSentryException without frames', () { + SentryException sentryException; + try { + throw StateError('a state error'); + } catch (err, _) { + sentryException = fixture.getSut().getSentryException( + err, + stackTrace: '', + ); + } + + expect(sentryException.type, 'StateError'); + expect(sentryException.stackTrace, isNull); + }); + + test('should not override event.stacktrace', () { + SentryException sentryException; + try { + throw StateError('a state error'); + } catch (err, _) { + sentryException = fixture.getSut().getSentryException( + err, + stackTrace: ''' +#0 baz (file:///pathto/test.dart:50:3) + +#1 bar (file:///pathto/test.dart:46:9) + ''', + ); + } + + expect(sentryException.type, 'StateError'); + expect(sentryException.stackTrace!.frames.first.lineNo, 46); + expect(sentryException.stackTrace!.frames.first.colNo, 9); + expect(sentryException.stackTrace!.frames.first.fileName, 'test.dart'); + }); + + test('should extract stackTrace from custom exception', () { + fixture.options + .addExceptionStackTraceExtractor(CustomExceptionStackTraceExtractor()); + + SentryException sentryException; + try { + throw CustomException(StackTrace.fromString(''' +#0 baz (file:///pathto/test.dart:50:3) + +#1 bar (file:///pathto/test.dart:46:9) + ''')); + } catch (err, _) { + sentryException = fixture.getSut().getSentryException( + err, + ); + } + + expect(sentryException.type, 'CustomException'); + expect(sentryException.stackTrace!.frames.first.lineNo, 46); + expect(sentryException.stackTrace!.frames.first.colNo, 9); + expect(sentryException.stackTrace!.frames.first.fileName, 'test.dart'); + }); + + test('should not fail when stackTrace property does not exist', () { + SentryException sentryException; + try { + throw Object(); + } catch (err, _) { + sentryException = fixture.getSut().getSentryException( + err, + ); + } + + expect(sentryException.type, 'Object'); + expect(sentryException.stackTrace, isNotNull); + }); + + test('getSentryException with not thrown Error and frames', () { + final sentryException = fixture.getSut().getSentryException( + CustomError(), + ); + + expect(sentryException.type, 'CustomError'); + expect(sentryException.stackTrace?.frames, isNotEmpty); + + // skip on browser because [StackTrace.current] still returns null + }, onPlatform: {'browser': Skip()}); + + test('getSentryException with not thrown Error and empty frames', () { + final sentryException = fixture + .getSut() + .getSentryException(CustomError(), stackTrace: StackTrace.empty); + + expect(sentryException.type, 'CustomError'); + expect(sentryException.stackTrace?.frames, isNotEmpty); + + // skip on browser because [StackTrace.current] still returns null + }, onPlatform: {'browser': Skip()}); + + test('reads the snapshot from the mechanism', () { + final error = StateError('test-error'); + final mechanism = Mechanism(type: 'Mechanism'); + final throwableMechanism = ThrowableMechanism( + mechanism, + error, + snapshot: true, + ); + + SentryException sentryException; + try { + throw throwableMechanism; + } catch (err, stackTrace) { + sentryException = fixture.getSut().getSentryException( + throwableMechanism, + stackTrace: stackTrace, + ); + } + + expect(sentryException.stackTrace!.snapshot, true); + }); + + test('getSentryException adds throwable', () { + SentryException sentryException; + dynamic throwable; + try { + throw StateError('a state error'); + } catch (err, stacktrace) { + throwable = err; + sentryException = fixture.getSut().getSentryException( + err, + stackTrace: stacktrace, + ); + } + + expect(sentryException.throwable, throwable); + }); + + test('should remove stackTrace string from value', () { + final stackTraceError = StackTraceError(); + final sentryException = fixture.getSut().getSentryException(stackTraceError, + stackTrace: StackTraceErrorStackTrace()); + final expected = + "NetworkError(type: NetworkErrorType.unknown, error: Instance of 'iH')"; + + expect(sentryException.value, expected); + }); + + test('no empty value', () { + final stackTraceError = StackTraceError(); + stackTraceError.prefix = ""; + final sentryException = fixture.getSut().getSentryException(stackTraceError, + stackTrace: StackTraceErrorStackTrace()); + + expect(sentryException.value, isNull); + }); + + test( + 'set snapshot to true when no stracktrace is present & attachStacktrace == true', + () { + final sentryException = + fixture.getSut(attachStacktrace: true).getSentryException(Object()); + + expect(sentryException.stackTrace!.snapshot, true); + }); + + test( + 'set snapshot to false when no stracktrace is present & attachStacktrace == false', + () { + final sentryException = + fixture.getSut(attachStacktrace: false).getSentryException(Object()); + + // stackTrace is null anyway when not present and attachStacktrace false + expect(sentryException.stackTrace?.snapshot, isNull); + }); + + test('sets stacktrace build id and image address', () { + final sentryException = fixture + .getSut(attachStacktrace: false) + .getSentryException(Object(), stackTrace: StackTraceErrorStackTrace()); + + final sentryStackTrace = sentryException.stackTrace!; + expect(sentryStackTrace.baseAddr, '0x752602b000'); + expect(sentryStackTrace.buildId, 'bca64abfdfcc84d231bb8f1ccdbfbd8d'); + }); + + test('sets null build id and image address if not present', () { + final sentryException = fixture + .getSut(attachStacktrace: false) + .getSentryException(Object(), stackTrace: null); + + // stackTrace is null anyway with null stack trace and attachStacktrace false + final sentryStackTrace = sentryException.stackTrace; + expect(sentryStackTrace?.baseAddr, isNull); + expect(sentryStackTrace?.buildId, isNull); + }); + + test('remove sentry frames', () { + final sentryException = + fixture.getSut(attachStacktrace: false).getSentryException( + SentryStackTraceError(), + stackTrace: SentryStackTrace(), + removeSentryFrames: true, + ); + + final sentryStackTrace = sentryException.stackTrace!; + expect(sentryStackTrace.baseAddr, isNull); + + expect(sentryStackTrace.frames.length, 17); + expect(sentryStackTrace.frames[16].package, 'sentry_flutter_example'); + expect(sentryStackTrace.frames[15].package, 'flutter'); + }); +} + +class CustomError extends Error {} + +class CustomException implements Exception { + final StackTrace stackTrace; + + CustomException(this.stackTrace); +} + +class CustomExceptionStackTraceExtractor + extends ExceptionStackTraceExtractor { + @override + StackTrace? stackTrace(CustomException error) { + return error.stackTrace; + } +} + +class StackTraceError extends Error { + var prefix = + "NetworkError(type: NetworkErrorType.unknown, error: Instance of 'iH')"; + + @override + String toString() { + return ''' +$prefix + +${StackTraceErrorStackTrace()}'''; + } +} + +class StackTraceErrorStackTrace implements StackTrace { + @override + String toString() { + return ''' +pid: 9437, tid: 10069, name 1.ui +os: android arch: arm64 comp: yes sim: no +build_id: 'bca64abfdfcc84d231bb8f1ccdbfbd8d' +isolate_dso_base: 752602b000, vm_dso_base: 752602b000 +isolate_instructions: 7526344980, vm_instructions: 752633f000 +#00 abs 00000075266c2fbf virt 0000000000697fbf _kDartIsolateSnapshotInstructions+0x37e63f +#1 abs 000000752685211f virt 000000000082711f _kDartIsolateSnapshotInstructions+0x50d79f +#2 abs 0000007526851cb3 virt 0000000000826cb3 _kDartIsolateSnapshotInstructions+0x50d333 +#3 abs 0000007526851c63 virt 0000000000826c63 _kDartIsolateSnapshotInstructions+0x50d2e3 +#4 abs 0000007526851bf3 virt 0000000000826bf3 _kDartIsolateSnapshotInstructions+0x50d273 +#5 abs 0000007526a0b44b virt 00000000009e044b _kDartIsolateSnapshotInstructions+0x6c6acb +#6 abs 0000007526a068a7 virt 00000000009db8a7 _kDartIsolateSnapshotInstructions+0x6c1f27 +#7 abs 0000007526b57a2b virt 0000000000b2ca2b _kDartIsolateSnapshotInstructions+0x8130ab +#8 abs 0000007526b5d93b virt 0000000000b3293b _kDartIsolateSnapshotInstructions+0x818fbb +#9 abs 0000007526a2333b virt 00000000009f833b _kDartIsolateSnapshotInstructions+0x6de9bb +#10 abs 0000007526937957 virt 000000000090c957 _kDartIsolateSnapshotInstructions+0x5f2fd7 +#11 abs 0000007526a243a3 virt 00000000009f93a3 _kDartIsolateSnapshotInstructions+0x6dfa23 +#12 abs 000000752636273b virt 000000000033773b _kDartIsolateSnapshotInstructions+0x1ddbb +#13 abs 0000007526a36ac3 virt 0000000000a0bac3 _kDartIsolateSnapshotInstructions+0x6f2143 +#14 abs 00000075263626af virt 00000000003376af _kDartIsolateSnapshotInstructions+0x1dd2f'''; + } +} + +class SentryStackTraceError extends Error { + var prefix = "Unknown error without own stacktrace"; + + @override + String toString() { + return ''' +$prefix + +${SentryStackTrace()}'''; + } +} + +class SentryStackTrace implements StackTrace { + @override + String toString() { + return ''' + #0 getCurrentStackTrace (package:sentry/src/utils/stacktrace_utils.dart:10:49) +#1 OnErrorIntegration.call. (package:sentry_flutter/src/integrations/on_error_integration.dart:82:22) +#2 MainScaffold.build. (package:sentry_flutter_example/main.dart:349:23) +#3 _InkResponseState.handleTap (package:flutter/src/material/ink_well.dart:1170:21) +#4 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:351:24) +#5 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:656:11) +#6 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:313:5) +#7 BaseTapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:283:7) +#8 GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:169:27) +#9 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:505:20) +#10 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:481:22) +#11 RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:450:11) +#12 GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:426:7) +#13 GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:389:5) +#14 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:336:7) +#15 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:305:9) +#16 _invoke1 (dart:ui/hooks.dart:328:13) +#17 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:442:7) +#18 _dispatchPointerDataPacket (dart:ui/hooks.dart:262:31) + '''; + } +} + +class Fixture { + final options = defaultTestOptions(); + + SentryExceptionFactory getSut({bool attachStacktrace = true}) { + options.attachStacktrace = attachStacktrace; + return SentryExceptionFactory(options); + } +} diff --git a/packages/dart/test/sentry_id_test.dart b/packages/dart/test/sentry_id_test.dart new file mode 100644 index 0000000000..23878f7082 --- /dev/null +++ b/packages/dart/test/sentry_id_test.dart @@ -0,0 +1,35 @@ +import 'package:sentry/src/protocol/sentry_id.dart'; +import 'package:test/test.dart'; + +void main() { + test('empty id', () { + expect(SentryId.empty().toString(), '00000000000000000000000000000000'); + }); + + test('empty id equals from empty id', () { + expect( + SentryId.empty(), + SentryId.fromId('00000000000000000000000000000000'), + ); + }); + + test('uuid format with dashes', () { + expect( + SentryId.fromId('00000000-0000-0000-0000-000000000000'), + SentryId.empty(), + ); + }); + + test('empty id equality', () { + expect(SentryId.empty(), SentryId.empty()); + }); + + test('id roundtrip', () { + final id = SentryId.newId(); + expect(id, SentryId.fromId(id.toString())); + }); + + test('newId should not be equal to newId', () { + expect(SentryId.newId() == SentryId.newId(), false); + }); +} diff --git a/packages/dart/test/sentry_io_client_test.dart b/packages/dart/test/sentry_io_client_test.dart new file mode 100644 index 0000000000..a775073b7c --- /dev/null +++ b/packages/dart/test/sentry_io_client_test.dart @@ -0,0 +1,15 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +@TestOn('vm') +library; + +import 'dart:io'; + +import 'package:test/test.dart'; + +import 'test_utils.dart'; + +void main() { + runTest(gzip: gzip); +} diff --git a/packages/dart/test/sentry_isolate_extension_test.dart b/packages/dart/test/sentry_isolate_extension_test.dart new file mode 100644 index 0000000000..8cffea9c1c --- /dev/null +++ b/packages/dart/test/sentry_isolate_extension_test.dart @@ -0,0 +1,60 @@ +@TestOn('vm') +library; + +import 'dart:isolate'; + +import 'package:sentry/src/sentry_isolate_extension.dart'; +import 'package:test/test.dart'; + +import 'mocks/mock_hub.dart'; +import 'test_utils.dart'; + +void main() { + group("SentryIsolate", () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('add error listener', () async { + final throwingClosure = (String message) async { + throw StateError(message); + }; + + final isolate = + await Isolate.spawn(throwingClosure, "message", paused: true); + isolate.addSentryErrorListener(hub: fixture.hub); + isolate.resume(isolate.pauseCapability!); + + await Future.delayed(Duration(milliseconds: 10)); + + expect(fixture.hub.captureEventCalls.first, isNotNull); + }); + + test('remove error listener', () async { + final throwingClosure = (String message) async { + throw StateError(message); + }; + + final isolate = + await Isolate.spawn(throwingClosure, "message", paused: true); + final port = isolate.addSentryErrorListener(hub: fixture.hub); + isolate.removeSentryErrorListener(port); + isolate.resume(isolate.pauseCapability!); + + await Future.delayed(Duration(milliseconds: 10)); + + expect(fixture.hub.captureEventCalls.isEmpty, true); + }); + }); +} + +class Fixture { + final hub = MockHub(); + final options = defaultTestOptions()..tracesSampleRate = 1.0; + + Isolate getSut() { + return Isolate.current; + } +} diff --git a/packages/dart/test/sentry_isolate_test.dart b/packages/dart/test/sentry_isolate_test.dart new file mode 100644 index 0000000000..597c1de795 --- /dev/null +++ b/packages/dart/test/sentry_isolate_test.dart @@ -0,0 +1,75 @@ +@TestOn('vm') +library; + +import 'package:sentry/src/hub.dart'; +import 'package:sentry/src/protocol/sentry_level.dart'; +import 'package:sentry/src/protocol/span_status.dart'; +import 'package:sentry/src/sentry_isolate.dart'; +import 'package:test/test.dart'; + +import 'mocks/mock_hub.dart'; +import 'mocks/mock_sentry_client.dart'; +import 'test_utils.dart'; + +void main() { + group("SentryIsolate", () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('adds error listener', () async { + final throwingClosure = (String message) async { + throw StateError(message); + }; + + await SentryIsolate.spawn(throwingClosure, "message", hub: fixture.hub); + await Future.delayed(Duration(milliseconds: 10)); + + expect(fixture.hub.captureEventCalls.first, isNotNull); + }); + + test('marks transaction as internal error if no status', () async { + final exception = StateError('error'); + final stackTrace = StackTrace.current.toString(); + + final hub = Hub(fixture.options); + final client = MockSentryClient(); + hub.bindClient(client); + + hub.startTransaction('name', 'operation', bindToScope: true); + + await SentryIsolate.handleIsolateError( + hub, [exception.toString(), stackTrace]); + + final span = hub.getSpan(); + + expect(span?.status, const SpanStatus.internalError()); + + await span?.finish(); + }); + + test('sets level to error instead of fatal', () async { + final exception = StateError('error'); + final stackTrace = StackTrace.current.toString(); + + final hub = Hub(fixture.options); + final client = MockSentryClient(); + hub.bindClient(client); + + fixture.options.markAutomaticallyCollectedErrorsAsFatal = false; + + await SentryIsolate.handleIsolateError( + hub, [exception.toString(), stackTrace]); + + final capturedEvent = client.captureEventCalls.last.event; + expect(capturedEvent.level, SentryLevel.error); + }); + }); +} + +class Fixture { + final hub = MockHub(); + final options = defaultTestOptions()..tracesSampleRate = 1.0; +} diff --git a/packages/dart/test/sentry_log_batcher_test.dart b/packages/dart/test/sentry_log_batcher_test.dart new file mode 100644 index 0000000000..5e9f7cf5f3 --- /dev/null +++ b/packages/dart/test/sentry_log_batcher_test.dart @@ -0,0 +1,148 @@ +import 'package:test/test.dart'; +import 'package:sentry/src/sentry_log_batcher.dart'; +import 'package:sentry/src/sentry_options.dart'; +import 'package:sentry/src/protocol/sentry_log.dart'; +import 'package:sentry/src/protocol/sentry_log_level.dart'; + +import 'mocks/mock_transport.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('added logs are flushed after timeout', () async { + final flushTimeout = Duration(milliseconds: 1); + + final batcher = fixture.getSut(flushTimeout: flushTimeout); + + final log = SentryLog( + timestamp: DateTime.now(), + level: SentryLogLevel.info, + body: 'test', + attributes: {}, + ); + final log2 = SentryLog( + timestamp: DateTime.now(), + level: SentryLogLevel.info, + body: 'test2', + attributes: {}, + ); + + batcher.addLog(log); + batcher.addLog(log2); + + expect(fixture.mockTransport.envelopes.length, 0); + + await Future.delayed(flushTimeout); + + expect(fixture.mockTransport.envelopes.length, 1); + + final envelopePayloadJson = (fixture.mockTransport).logs.first; + + expect(envelopePayloadJson, isNotNull); + expect(envelopePayloadJson['items'].length, 2); + expect(envelopePayloadJson['items'].first['body'], log.body); + expect(envelopePayloadJson['items'].last['body'], log2.body); + }); + + test('max logs are flushed without timeout', () async { + final batcher = fixture.getSut(maxBufferSize: 10); + + final log = SentryLog( + timestamp: DateTime.now(), + level: SentryLogLevel.info, + body: 'test', + attributes: {}, + ); + + for (var i = 0; i < 10; i++) { + batcher.addLog(log); + } + + // Just wait a little bit, as we call capture without awaiting internally. + await Future.delayed(Duration(milliseconds: 1)); + + expect(fixture.mockTransport.envelopes.length, 1); + final envelopePayloadJson = (fixture.mockTransport).logs.first; + + expect(envelopePayloadJson, isNotNull); + expect(envelopePayloadJson['items'].length, 10); + }); + + test('more than max logs are flushed eventuelly', () async { + final flushTimeout = Duration(milliseconds: 100); + final batcher = fixture.getSut( + maxBufferSize: 10, + flushTimeout: flushTimeout, + ); + + final log = SentryLog( + timestamp: DateTime.now(), + level: SentryLogLevel.info, + body: 'test', + attributes: {}, + ); + + for (var i = 0; i < 15; i++) { + batcher.addLog(log); + } + + await Future.delayed(flushTimeout); + + expect(fixture.mockTransport.envelopes.length, 2); + + final firstEnvelopePayloadJson = (fixture.mockTransport).logs.first; + + expect(firstEnvelopePayloadJson, isNotNull); + expect(firstEnvelopePayloadJson['items'].length, 10); + + final secondEnvelopePayloadJson = (fixture.mockTransport).logs.last; + + expect(secondEnvelopePayloadJson, isNotNull); + expect(secondEnvelopePayloadJson['items'].length, 5); + }); + + test('calling flush directly flushes logs', () async { + final batcher = fixture.getSut(); + + final log = SentryLog( + timestamp: DateTime.now(), + level: SentryLogLevel.info, + body: 'test', + attributes: {}, + ); + + batcher.addLog(log); + batcher.addLog(log); + batcher.flush(); + + // Just wait a little bit, as we call capture without awaiting internally. + await Future.delayed(Duration(milliseconds: 1)); + + expect(fixture.mockTransport.envelopes.length, 1); + final envelopePayloadJson = (fixture.mockTransport).logs.first; + + expect(envelopePayloadJson, isNotNull); + expect(envelopePayloadJson['items'].length, 2); + }); +} + +class Fixture { + final options = SentryOptions(); + final mockTransport = MockTransport(); + + Fixture() { + options.transport = mockTransport; + } + + SentryLogBatcher getSut({Duration? flushTimeout, int? maxBufferSize}) { + return SentryLogBatcher( + options, + flushTimeout: flushTimeout, + maxBufferSize: maxBufferSize, + ); + } +} diff --git a/packages/dart/test/sentry_logger_formatter_test.dart b/packages/dart/test/sentry_logger_formatter_test.dart new file mode 100644 index 0000000000..946eee19f2 --- /dev/null +++ b/packages/dart/test/sentry_logger_formatter_test.dart @@ -0,0 +1,307 @@ +import 'package:test/test.dart'; +import 'package:sentry/src/sentry_logger_formatter.dart'; +import 'package:sentry/src/sentry_logger.dart'; +import 'package:sentry/src/protocol/sentry_log_attribute.dart'; + +void main() { + final fixture = Fixture(); + + void verifyPassedAttributes(Map attributes) { + expect(attributes['foo'].type, 'string'); + expect(attributes['foo'].value, 'bar'); + } + + void verifyBasicTemplate(String body, Map attributes) { + expect(body, 'Hello, World!'); + expect(attributes['sentry.message.template'].type, 'string'); + expect(attributes['sentry.message.template'].value, 'Hello, %s!'); + expect(attributes['sentry.message.parameter.0'].type, 'string'); + expect(attributes['sentry.message.parameter.0'].value, 'World'); + verifyPassedAttributes(attributes); + } + + void verifyTemplateWithMultipleArguments( + String body, Map attributes) { + expect(body, 'Name: Alice, Age: 30, Active: true, Score: 95.5'); + expect(attributes['sentry.message.template'].type, 'string'); + expect(attributes['sentry.message.template'].value, + 'Name: %s, Age: %s, Active: %s, Score: %s'); + expect(attributes['sentry.message.parameter.0'].type, 'string'); + expect(attributes['sentry.message.parameter.0'].value, 'Alice'); + expect(attributes['sentry.message.parameter.1'].type, 'integer'); + expect(attributes['sentry.message.parameter.1'].value, 30); + expect(attributes['sentry.message.parameter.2'].type, 'boolean'); + expect(attributes['sentry.message.parameter.2'].value, true); + expect(attributes['sentry.message.parameter.3'].type, 'double'); + expect(attributes['sentry.message.parameter.3'].value, 95.5); + verifyPassedAttributes(attributes); + } + + group('format basic template', () { + test('for trace', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.trace( + "Hello, %s!", + ["World"], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.traceCalls.length, 1); + final message = logger.traceCalls[0].message; + final attributes = logger.traceCalls[0].attributes!; + verifyBasicTemplate(message, attributes); + }); + + test('for debug', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.debug( + "Hello, %s!", + ["World"], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.debugCalls.length, 1); + final message = logger.debugCalls[0].message; + final attributes = logger.debugCalls[0].attributes!; + verifyBasicTemplate(message, attributes); + }); + + test('for info', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.info( + "Hello, %s!", + ["World"], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.infoCalls.length, 1); + final message = logger.infoCalls[0].message; + final attributes = logger.infoCalls[0].attributes!; + verifyBasicTemplate(message, attributes); + }); + + test('for warn', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.warn( + "Hello, %s!", + ["World"], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.warnCalls.length, 1); + final message = logger.warnCalls[0].message; + final attributes = logger.warnCalls[0].attributes!; + verifyBasicTemplate(message, attributes); + }); + + test('for error', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.error( + "Hello, %s!", + ["World"], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.errorCalls.length, 1); + final message = logger.errorCalls[0].message; + final attributes = logger.errorCalls[0].attributes!; + verifyBasicTemplate(message, attributes); + }); + + test('for fatal', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.fatal( + "Hello, %s!", + ["World"], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.fatalCalls.length, 1); + final message = logger.fatalCalls[0].message; + final attributes = logger.fatalCalls[0].attributes!; + verifyBasicTemplate(message, attributes); + }); + }); + + group('template with multiple arguments', () { + test('for trace', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.trace( + "Name: %s, Age: %s, Active: %s, Score: %s", + ['Alice', 30, true, 95.5], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.traceCalls.length, 1); + final message = logger.traceCalls[0].message; + final attributes = logger.traceCalls[0].attributes!; + verifyTemplateWithMultipleArguments(message, attributes); + }); + + test('for trace', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.trace( + "Name: %s, Age: %s, Active: %s, Score: %s", + ['Alice', 30, true, 95.5], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.traceCalls.length, 1); + final message = logger.traceCalls[0].message; + final attributes = logger.traceCalls[0].attributes!; + verifyTemplateWithMultipleArguments(message, attributes); + }); + + test('for debug', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.debug( + "Name: %s, Age: %s, Active: %s, Score: %s", + ['Alice', 30, true, 95.5], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.debugCalls.length, 1); + final message = logger.debugCalls[0].message; + final attributes = logger.debugCalls[0].attributes!; + verifyTemplateWithMultipleArguments(message, attributes); + }); + + test('for info', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.info( + "Name: %s, Age: %s, Active: %s, Score: %s", + ['Alice', 30, true, 95.5], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.infoCalls.length, 1); + final message = logger.infoCalls[0].message; + final attributes = logger.infoCalls[0].attributes!; + verifyTemplateWithMultipleArguments(message, attributes); + }); + + test('for warn', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.warn( + "Name: %s, Age: %s, Active: %s, Score: %s", + ['Alice', 30, true, 95.5], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.warnCalls.length, 1); + final message = logger.warnCalls[0].message; + final attributes = logger.warnCalls[0].attributes!; + verifyTemplateWithMultipleArguments(message, attributes); + }); + + test('for error', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.error( + "Name: %s, Age: %s, Active: %s, Score: %s", + ['Alice', 30, true, 95.5], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.errorCalls.length, 1); + final message = logger.errorCalls[0].message; + final attributes = logger.errorCalls[0].attributes!; + verifyTemplateWithMultipleArguments(message, attributes); + }); + + test('for fatal', () { + final logger = MockLogger(); + final sut = fixture.getSut(logger); + + sut.fatal( + "Name: %s, Age: %s, Active: %s, Score: %s", + ['Alice', 30, true, 95.5], + attributes: {'foo': SentryLogAttribute.string('bar')}, + ); + + expect(logger.fatalCalls.length, 1); + final message = logger.fatalCalls[0].message; + final attributes = logger.fatalCalls[0].attributes!; + verifyTemplateWithMultipleArguments(message, attributes); + }); + }); +} + +class Fixture { + SentryLoggerFormatter getSut(SentryLogger logger) { + return SentryLoggerFormatter(logger); + } +} + +class MockLogger implements SentryLogger { + var traceCalls = []; + var debugCalls = []; + var infoCalls = []; + var warnCalls = []; + var errorCalls = []; + var fatalCalls = []; + + @override + SentryLoggerFormatter get fmt => throw UnimplementedError(); + + @override + Future trace(String message, {Map? attributes}) async { + traceCalls.add((message: message, attributes: attributes)); + return; + } + + @override + Future debug(String message, {Map? attributes}) async { + debugCalls.add((message: message, attributes: attributes)); + return; + } + + @override + Future info(String message, {Map? attributes}) async { + infoCalls.add((message: message, attributes: attributes)); + return; + } + + @override + Future warn(String message, {Map? attributes}) async { + warnCalls.add((message: message, attributes: attributes)); + return; + } + + @override + Future error(String message, {Map? attributes}) async { + errorCalls.add((message: message, attributes: attributes)); + return; + } + + @override + Future fatal(String message, {Map? attributes}) async { + fatalCalls.add((message: message, attributes: attributes)); + return; + } +} + +typedef LoggerCall = ({String message, Map? attributes}); diff --git a/packages/dart/test/sentry_logger_test.dart b/packages/dart/test/sentry_logger_test.dart new file mode 100644 index 0000000000..96e4aa7743 --- /dev/null +++ b/packages/dart/test/sentry_logger_test.dart @@ -0,0 +1,95 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; +import 'test_utils.dart'; +import 'mocks/mock_hub.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + void verifyCaptureLog(SentryLogLevel level) { + expect(fixture.hub.captureLogCalls.length, 1); + final capturedLog = fixture.hub.captureLogCalls[0].log; + + expect(capturedLog.timestamp, fixture.timestamp); + expect(capturedLog.level, level); + expect(capturedLog.body, 'test'); + expect(capturedLog.attributes, fixture.attributes); + } + + test('sentry logger', () { + final logger = fixture.getSut(); + + logger.info('test', attributes: fixture.attributes); + + verifyCaptureLog(SentryLogLevel.info); + }); + + test('trace', () { + final logger = fixture.getSut(); + + logger.info('test', attributes: fixture.attributes); + + verifyCaptureLog(SentryLogLevel.info); + }); + + test('debug', () { + final logger = fixture.getSut(); + + logger.debug('test', attributes: fixture.attributes); + + verifyCaptureLog(SentryLogLevel.debug); + }); + + test('info', () { + final logger = fixture.getSut(); + + logger.info('test', attributes: fixture.attributes); + + verifyCaptureLog(SentryLogLevel.info); + }); + + test('warn', () { + final logger = fixture.getSut(); + + logger.warn('test', attributes: fixture.attributes); + + verifyCaptureLog(SentryLogLevel.warn); + }); + + test('error', () { + final logger = fixture.getSut(); + + logger.error('test', attributes: fixture.attributes); + + verifyCaptureLog(SentryLogLevel.error); + }); + + test('fatal', () { + final logger = fixture.getSut(); + + logger.fatal('test', attributes: fixture.attributes); + + verifyCaptureLog(SentryLogLevel.fatal); + }); +} + +class Fixture { + final options = defaultTestOptions(); + final hub = MockHub(); + final timestamp = DateTime.fromMicrosecondsSinceEpoch(0); + + final attributes = { + 'string': SentryLogAttribute.string('string'), + 'int': SentryLogAttribute.int(1), + 'double': SentryLogAttribute.double(1.0), + 'bool': SentryLogAttribute.bool(true), + }; + + SentryLogger getSut() { + return SentryLogger(() => timestamp, hub: hub); + } +} diff --git a/packages/dart/test/sentry_measurement_test.dart b/packages/dart/test/sentry_measurement_test.dart new file mode 100644 index 0000000000..1dddee645a --- /dev/null +++ b/packages/dart/test/sentry_measurement_test.dart @@ -0,0 +1,42 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + group('$SentryMeasurement', () { + test('total frames has none unit', () { + expect( + SentryMeasurement.totalFrames(10).unit, SentryMeasurementUnit.none); + }); + + test('slow frames has none unit', () { + expect(SentryMeasurement.slowFrames(10).unit, SentryMeasurementUnit.none); + }); + + test('frozen frames has none unit', () { + expect( + SentryMeasurement.frozenFrames(10).unit, SentryMeasurementUnit.none); + }); + + test('warm start has milliseconds unit', () { + expect(SentryMeasurement.warmAppStart(Duration(seconds: 1)).unit, + DurationSentryMeasurementUnit.milliSecond); + }); + + test('cold start has milliseconds unit', () { + expect(SentryMeasurement.coldAppStart(Duration(seconds: 1)).unit, + DurationSentryMeasurementUnit.milliSecond); + }); + + test('toJson sets unit if given', () { + final measurement = SentryMeasurement('name', 10, + unit: DurationSentryMeasurementUnit.milliSecond); + final map = { + 'value': 10, + 'unit': 'millisecond', + }; + + expect(MapEquality().equals(measurement.toJson(), map), true); + }); + }); +} diff --git a/packages/dart/test/sentry_measurement_unit_test.dart b/packages/dart/test/sentry_measurement_unit_test.dart new file mode 100644 index 0000000000..e0ca1cb5b8 --- /dev/null +++ b/packages/dart/test/sentry_measurement_unit_test.dart @@ -0,0 +1,130 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + group('$SentryMeasurementUnit', () { + group('DurationUnit', () { + test('nanosecond', () { + expect(DurationSentryMeasurementUnit.nanoSecond.toStringValue(), + 'nanosecond'); + }); + + test('microsecond', () { + expect(DurationSentryMeasurementUnit.microSecond.toStringValue(), + 'microsecond'); + }); + + test('millisecond', () { + expect(DurationSentryMeasurementUnit.milliSecond.toStringValue(), + 'millisecond'); + }); + + test('second', () { + expect(DurationSentryMeasurementUnit.second.toStringValue(), 'second'); + }); + + test('minute', () { + expect(DurationSentryMeasurementUnit.minute.toStringValue(), 'minute'); + }); + + test('hour', () { + expect(DurationSentryMeasurementUnit.hour.toStringValue(), 'hour'); + }); + + test('day', () { + expect(DurationSentryMeasurementUnit.day.toStringValue(), 'day'); + }); + + test('week', () { + expect(DurationSentryMeasurementUnit.week.toStringValue(), 'week'); + }); + }); + + group('FractionUnit', () { + test('ratio', () { + expect(FractionSentryMeasurementUnit.ratio.toStringValue(), 'ratio'); + }); + + test('percent', () { + expect( + FractionSentryMeasurementUnit.percent.toStringValue(), 'percent'); + }); + }); + + group('None', () { + test('none', () { + expect(SentryMeasurementUnit.none.toStringValue(), 'none'); + }); + }); + + group('InformationUnit', () { + test('bit', () { + expect(InformationSentryMeasurementUnit.bit.toStringValue(), 'bit'); + }); + + test('byte', () { + expect(InformationSentryMeasurementUnit.byte.toStringValue(), 'byte'); + }); + + test('kilobyte', () { + expect(InformationSentryMeasurementUnit.kiloByte.toStringValue(), + 'kilobyte'); + }); + + test('kibibyte', () { + expect(InformationSentryMeasurementUnit.kibiByte.toStringValue(), + 'kibibyte'); + }); + + test('megabyte', () { + expect(InformationSentryMeasurementUnit.megaByte.toStringValue(), + 'megabyte'); + }); + + test('mebibyte', () { + expect(InformationSentryMeasurementUnit.mebiByte.toStringValue(), + 'mebibyte'); + }); + + test('gigabyte', () { + expect(InformationSentryMeasurementUnit.gigaByte.toStringValue(), + 'gigabyte'); + }); + + test('gibibyte', () { + expect(InformationSentryMeasurementUnit.gibiByte.toStringValue(), + 'gibibyte'); + }); + test('terabyte', () { + expect(InformationSentryMeasurementUnit.teraByte.toStringValue(), + 'terabyte'); + }); + test('tebibyte', () { + expect(InformationSentryMeasurementUnit.tebiByte.toStringValue(), + 'tebibyte'); + }); + test('petabyte', () { + expect(InformationSentryMeasurementUnit.petaByte.toStringValue(), + 'petabyte'); + }); + test('pebibyte', () { + expect(InformationSentryMeasurementUnit.pebiByte.toStringValue(), + 'pebibyte'); + }); + test('exabyte', () { + expect(InformationSentryMeasurementUnit.exaByte.toStringValue(), + 'exabyte'); + }); + test('exbibyte', () { + expect(InformationSentryMeasurementUnit.exbiByte.toStringValue(), + 'exbibyte'); + }); + }); + + group('Custom', () { + test('custom', () { + expect(CustomSentryMeasurementUnit('custom').toStringValue(), 'custom'); + }); + }); + }); +} diff --git a/packages/dart/test/sentry_options_test.dart b/packages/dart/test/sentry_options_test.dart new file mode 100644 index 0000000000..25402e2f74 --- /dev/null +++ b/packages/dart/test/sentry_options_test.dart @@ -0,0 +1,186 @@ +import 'package:http/http.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/noop_client.dart'; +import 'package:sentry/src/version.dart'; +import 'package:test/test.dart'; + +import 'test_utils.dart'; + +void main() { + test('$Client is NoOp', () { + final options = defaultTestOptions(); + expect(NoOpClient(), options.httpClient); + }); + + test('$Client sets a custom client', () { + final options = defaultTestOptions(); + + final client = Client(); + options.httpClient = client; + expect(client, options.httpClient); + }); + + test('maxBreadcrumbs is 100 by default', () { + final options = defaultTestOptions(); + + expect(100, options.maxBreadcrumbs); + }); + + test('maxBreadcrumbs sets custom maxBreadcrumbs', () { + final options = defaultTestOptions(); + options.maxBreadcrumbs = 200; + + expect(200, options.maxBreadcrumbs); + }); + + test('SdkLogger sets a diagnostic logger', () { + final options = defaultTestOptions(); + expect(options.log, noOpLog); + options.debug = true; + + expect(options.log, isNot(noOpLog)); + }); + + test('setting debug correctly sets logger', () { + final options = defaultTestOptions(); + expect(options.log, noOpLog); + expect(options.diagnosticLog, isNull); + options.debug = true; + expect(options.log, isNot(options.debugLog)); + expect(options.diagnosticLog!.logger, options.debugLog); + expect(options.log, options.diagnosticLog!.log); + + options.debug = false; + expect(options.log, isNot(noOpLog)); + expect(options.diagnosticLog!.logger, noOpLog); + expect(options.log, options.diagnosticLog!.log); + + options.debug = true; + expect(options.log, isNot(options.debugLog)); + expect(options.diagnosticLog!.logger, options.debugLog); + expect(options.log, options.diagnosticLog!.log); + }); + + test('tracesSampler is null by default', () { + final options = defaultTestOptions(); + + expect(options.tracesSampler, isNull); + }); + + test('tracesSampleRate is null by default', () { + final options = defaultTestOptions(); + + expect(options.tracesSampleRate, isNull); + }); + + test('isTracingEnabled is disabled', () { + final options = defaultTestOptions(); + + expect(options.isTracingEnabled(), false); + }); + + test('isTracingEnabled is enabled by theres rate', () { + final options = defaultTestOptions(); + options.tracesSampleRate = 1.0; + + expect(options.isTracingEnabled(), true); + }); + + test('isTracingEnabled is enabled by theres sampler', () { + final options = defaultTestOptions(); + + double? sampler(SentrySamplingContext samplingContext) => 0.0; + + options.tracesSampler = sampler; + + expect(options.isTracingEnabled(), true); + }); + + test('SentryOptions empty inits the late var', () { + final options = SentryOptions.empty(); + options.sdk.addPackage('test', '1.2.3'); + + expect( + options.sdk.packages + .where((element) => + element.name == 'test' && element.version == '1.2.3') + .isNotEmpty, + true); + }); + + test('SentryOptions has all targets by default', () { + final options = SentryOptions.empty(); + + expect(options.tracePropagationTargets, ['.*']); + }); + + test('SentryOptions has sentryClientName set', () { + final options = defaultTestOptions(); + + expect(options.sentryClientName, + '${sdkName(options.platform.isWeb)}/$sdkVersion'); + }); + + test('SentryOptions has default idleTimeout', () { + final options = SentryOptions.empty(); + + expect(options.idleTimeout?.inSeconds, Duration(seconds: 3).inSeconds); + }); + + test('Spotlight is disabled by default', () { + final options = defaultTestOptions(); + + expect(options.spotlight.enabled, false); + }); + + test('enableExceptionTypeIdentification is enabled by default', () { + final options = defaultTestOptions(); + + expect(options.enableExceptionTypeIdentification, true); + }); + + test('enablePureDartSymbolication is enabled by default', () { + final options = defaultTestOptions(); + + expect(options.enableDartSymbolication, true); + }); + + test('diagnosticLevel is warning by default', () { + final options = defaultTestOptions(); + + expect(options.diagnosticLevel, SentryLevel.warning); + }); + + test('parsedDsn is correctly parsed and cached', () { + final options = defaultTestOptions(); + + // Access parsedDsn for the first time + final parsedDsn1 = options.parsedDsn; + + // Access parsedDsn again + final parsedDsn2 = options.parsedDsn; + + // Should return the same instance since it's cached + expect(identical(parsedDsn1, parsedDsn2), isTrue); + + // Verify the parsed DSN fields + final manuallyParsedDsn = Dsn.parse(options.dsn!); + expect(parsedDsn1.publicKey, manuallyParsedDsn.publicKey); + expect(parsedDsn1.postUri, manuallyParsedDsn.postUri); + expect(parsedDsn1.secretKey, manuallyParsedDsn.secretKey); + expect(parsedDsn1.projectId, manuallyParsedDsn.projectId); + expect(parsedDsn1.uri, manuallyParsedDsn.uri); + }); + + test('parsedDsn throws when DSN is null', () { + final options = defaultTestOptions()..dsn = null; + + expect(() => options.parsedDsn, throwsA(isA())); + }); + + test('parsedDsn throws when DSN is empty', () { + final options = defaultTestOptions()..dsn = ''; + + expect(() => options.parsedDsn, throwsA(isA())); + }); +} diff --git a/packages/dart/test/sentry_run_zoned_guarded_test.dart b/packages/dart/test/sentry_run_zoned_guarded_test.dart new file mode 100644 index 0000000000..1fd06310dd --- /dev/null +++ b/packages/dart/test/sentry_run_zoned_guarded_test.dart @@ -0,0 +1,123 @@ +@TestOn('vm') +library; + +import 'dart:async'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_run_zoned_guarded.dart'; +import 'package:test/test.dart'; + +import 'mocks/mock_hub.dart'; +import 'mocks/mock_sentry_client.dart'; +import 'test_utils.dart'; + +void main() { + group("$SentryRunZonedGuarded", () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('calls onError', () async { + final error = StateError("StateError"); + var onErrorCalled = false; + + final completer = Completer(); + SentryRunZonedGuarded.sentryRunZonedGuarded( + fixture.hub, + () { + throw error; + }, + (error, stackTrace) { + onErrorCalled = true; + completer.complete(); + }, + ); + + await completer.future; + + expect(onErrorCalled, true); + }); + + test('calls zoneSpecification print', () async { + var printCalled = false; + final completer = Completer(); + + final zoneSpecification = ZoneSpecification( + print: (self, parent, zone, line) { + printCalled = true; + completer.complete(); + }, + ); + SentryRunZonedGuarded.sentryRunZonedGuarded( + fixture.hub, + () { + print('foo'); + }, + null, + zoneSpecification: zoneSpecification, + ); + + await completer.future; + + expect(printCalled, true); + }); + + test('marks transaction as internal error if no status', () async { + final exception = StateError('error'); + + final client = MockSentryClient(); + final hub = Hub(fixture.options); + hub.bindClient(client); + hub.startTransaction('name', 'operation', bindToScope: true); + + final completer = Completer(); + SentryRunZonedGuarded.sentryRunZonedGuarded( + hub, + () { + throw exception; + }, + (error, stackTrace) { + completer.complete(); + }, + ); + + await completer.future; + + final span = hub.getSpan(); + expect(span?.status, const SpanStatus.internalError()); + await span?.finish(); + }); + + test('sets level to error instead of fatal', () async { + final client = MockSentryClient(); + final hub = Hub(fixture.options); + hub.bindClient(client); + fixture.options.markAutomaticallyCollectedErrorsAsFatal = false; + + final exception = StateError('error'); + + final completer = Completer(); + SentryRunZonedGuarded.sentryRunZonedGuarded( + hub, + () { + throw exception; + }, + (error, stackTrace) { + completer.complete(); + }, + ); + + await completer.future; + + final capturedEvent = client.captureEventCalls.last.event; + expect(capturedEvent.level, SentryLevel.error); + }); + }); +} + +class Fixture { + final hub = MockHub(); + final options = defaultTestOptions()..tracesSampleRate = 1.0; +} diff --git a/packages/dart/test/sentry_span_context_test.dart b/packages/dart/test/sentry_span_context_test.dart new file mode 100644 index 0000000000..81e651dc6e --- /dev/null +++ b/packages/dart/test/sentry_span_context_test.dart @@ -0,0 +1,47 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final fixture = Fixture(); + + test('toJson serializes', () { + final sut = fixture.getSut(); + + final map = sut.toJson(); + + expect(map['span_id'], isNotNull); + expect(map['trace_id'], isNotNull); + expect(map['op'], 'op'); + expect(map['parent_span_id'], isNotNull); + expect(map['description'], 'desc'); + expect(map['origin'], 'manual'); + }); + + test('toTraceContext gets sampled, status, and origin', () { + final sut = fixture.getSut(); + final aborted = SpanStatus.aborted(); + final traceContext = sut.toTraceContext( + sampled: true, + status: aborted, + ); + + expect(traceContext.sampled, true); + expect(traceContext.spanId, isNotNull); + expect(traceContext.traceId, isNotNull); + expect(traceContext.operation, 'op'); + expect(traceContext.parentSpanId, isNotNull); + expect(traceContext.description, 'desc'); + expect(traceContext.status, aborted); + expect(traceContext.origin, 'manual'); + }); +} + +class Fixture { + SentrySpanContext getSut() { + return SentrySpanContext( + operation: 'op', + parentSpanId: SpanId.newId(), + description: 'desc', + origin: 'manual'); + } +} diff --git a/packages/dart/test/sentry_span_test.dart b/packages/dart/test/sentry_span_test.dart new file mode 100644 index 0000000000..7b3486baea --- /dev/null +++ b/packages/dart/test/sentry_span_test.dart @@ -0,0 +1,316 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:test/test.dart'; + +import 'mocks/mock_hub.dart'; + +void main() { + final fixture = Fixture(); + + test('convert given startTimestamp to utc date time', () async { + final nonUtcStartTimestamp = DateTime.now().toLocal(); + + final sut = fixture.getSut(startTimestamp: nonUtcStartTimestamp); + + expect(nonUtcStartTimestamp.isUtc, false); + expect(sut.startTimestamp.isUtc, true); + }); + + test('convert given endTimestamp to utc date time', () async { + final nonUtcEndTimestamp = DateTime.now().toLocal(); + + final sut = fixture.getSut(startTimestamp: nonUtcEndTimestamp); + + await sut.finish(endTimestamp: nonUtcEndTimestamp); + + expect(nonUtcEndTimestamp.isUtc, false); + expect(sut.endTimestamp?.isUtc, true); + }); + + test('finish sets status', () async { + final sut = fixture.getSut(); + + await sut.finish(status: SpanStatus.aborted()); + + expect(sut.status, SpanStatus.aborted()); + }); + + test('finish sets end timestamp', () async { + final sut = fixture.getSut(); + expect(sut.endTimestamp, isNull); + await sut.finish(); + + expect(sut.endTimestamp, isNotNull); + }); + + test('finish uses given end timestamp', () async { + final sut = fixture.getSut(); + final endTimestamp = getUtcDateTime(); + + expect(sut.endTimestamp, isNull); + await sut.finish(endTimestamp: endTimestamp); + expect(sut.endTimestamp, endTimestamp); + }); + + test('finish sets throwable', () async { + final sut = fixture.getSut(); + sut.throwable = StateError('message'); + + await sut.finish(); + + expect(fixture.hub.spanContextCals, 1); + }); + + test( + 'finish does not set endTimestamp if given end timestamp is before start timestamp', + () async { + final sut = fixture.getSut(); + + final invalidEndTimestamp = sut.startTimestamp.add(-Duration(hours: 1)); + await sut.finish(endTimestamp: invalidEndTimestamp); + + expect(sut.endTimestamp, isNot(equals(invalidEndTimestamp))); + }); + + test('span adds data', () { + final sut = fixture.getSut(); + + sut.setData('test', 'test'); + + expect(sut.data['test'], 'test'); + }); + + test('span removes data', () { + final sut = fixture.getSut(); + + sut.setData('test', 'test'); + sut.removeData('test'); + + expect(sut.data['test'], isNull); + }); + + test('span sets origin', () { + final sut = fixture.getSut(); + + sut.origin = 'manual'; + + expect(sut.origin, 'manual'); + }); + + test('span adds tag', () { + final sut = fixture.getSut(); + + sut.setTag('test', 'test'); + + expect(sut.tags['test'], 'test'); + }); + + test('span removes tags', () { + final sut = fixture.getSut(); + + sut.setTag('test', 'test'); + sut.removeTag('test'); + + expect(sut.tags['test'], isNull); + }); + + test('span starts child', () { + final sut = fixture.getSut(); + + final child = sut.startChild('op', description: 'desc'); + + expect(child.context.parentSpanId, fixture.context.spanId); + expect(child.context.operation, 'op'); + expect(child.context.description, 'desc'); + }); + + test('span serializes', () async { + final sut = fixture.getSut(); + + sut.setTag('test', 'test'); + sut.setData('test', 'test'); + sut.origin = 'manual'; + + await sut.finish(status: SpanStatus.aborted()); + + final map = sut.toJson(); + + expect(map['start_timestamp'], isNotNull); + expect(map['timestamp'], isNotNull); + expect(map['data']['test'], 'test'); + expect(map['tags']['test'], 'test'); + expect(map['status'], 'aborted'); + expect(map['origin'], 'manual'); + }); + + test('finished returns false if not yet', () { + final sut = fixture.getSut(); + + expect(sut.finished, false); + }); + + test('finished returns true if finished', () async { + final sut = fixture.getSut(); + await sut.finish(); + + expect(sut.finished, true); + }); + + test('toSentryTrace returns trace header', () { + final sut = fixture.getSut(); + + expect(sut.toSentryTrace().value, + '${sut.context.traceId}-${sut.context.spanId}-1'); + }); + + test('finish isnt allowed to be called twice', () async { + final sut = fixture.getSut(); + + await sut.finish(status: SpanStatus.ok()); + await sut.finish(status: SpanStatus.cancelled()); + + expect(sut.status, SpanStatus.ok()); + }); + + test('removeData isnt allowed to be called after finishing', () async { + final sut = fixture.getSut(); + + sut.setData('key', 'value'); + await sut.finish(status: SpanStatus.ok()); + sut.removeData('key'); + + expect(sut.data['key'], 'value'); + }); + + test('removeTag isnt allowed to be called after finishing', () async { + final sut = fixture.getSut(); + + sut.setTag('key', 'value'); + await sut.finish(status: SpanStatus.ok()); + sut.removeTag('key'); + + expect(sut.tags['key'], 'value'); + }); + + test('setData isnt allowed to be called after finishing', () async { + final sut = fixture.getSut(); + + sut.setData('key', 'value'); + await sut.finish(status: SpanStatus.ok()); + sut.setData('key', 'value2'); + + expect(sut.data['key'], 'value'); + }); + + test('setTag isnt allowed to be called after finishing', () async { + final sut = fixture.getSut(); + + sut.setTag('key', 'value'); + await sut.finish(status: SpanStatus.ok()); + sut.setTag('key', 'value2'); + + expect(sut.tags['key'], 'value'); + }); + + test('startChild isnt allowed to be called after finishing', () async { + final sut = fixture.getSut(); + + await sut.finish(status: SpanStatus.ok()); + final span = sut.startChild('op'); + + expect(NoOpSentrySpan(), span); + }); + + test( + 'startChild isnt allowed to be called if childs startTimestamp is before parents', + () async { + final parentStartTimestamp = DateTime.now(); + final childStartTimestamp = parentStartTimestamp.add(-Duration(hours: 1)); + final sut = fixture.getSut(startTimestamp: parentStartTimestamp); + + final span = sut.startChild('op', startTimestamp: childStartTimestamp); + + expect(NoOpSentrySpan(), span); + }); + + test('callback called on finish', () async { + var numberOfCallbackCalls = 0; + final passedHint = Hint(); + + final sut = fixture.getSut( + finishedCallback: ({DateTime? endTimestamp, Hint? hint}) async { + expect(passedHint, hint); + numberOfCallbackCalls += 1; + }); + + await sut.finish(hint: passedHint); + + expect(numberOfCallbackCalls, 1); + }); + + test('optional endTimestamp set instead of current time', () async { + final sut = fixture.getSut(); + + final endTimestamp = getUtcDateTime().add(Duration(days: 1)); + + await sut.finish(endTimestamp: endTimestamp); + + expect(sut.endTimestamp, endTimestamp); + }); + + test('child span reschedule finish timer', () async { + final sut = fixture.getSut(autoFinishAfter: Duration(seconds: 5)); + + final currentTimer = fixture.tracer.autoFinishAfterTimer!; + + sut.scheduleFinish(); + + final newTimer = fixture.tracer.autoFinishAfterTimer!; + + expect(currentTimer, isNot(equals(newTimer))); + }); + + test('takes origin from context', () async { + final sut = fixture.getSut(); + expect(sut.origin, 'manual'); + }); + + test('setMeasurement sets a measurement', () async { + final sut = fixture.getSut(); + sut.setMeasurement("test", 1); + expect(sut.tracer.measurements.containsKey("test"), true); + expect(sut.tracer.measurements["test"]!.value, 1); + }); + + test('setMeasurement does not set a measurement if a span is finished', + () async { + final sut = fixture.getSut(); + await sut.finish(); + sut.setMeasurement("test", 1); + expect(sut.tracer.measurements.isEmpty, true); + }); +} + +class Fixture { + final context = SentryTransactionContext('name', 'op', origin: 'manual'); + late SentryTracer tracer; + final hub = MockHub(); + + SentrySpan getSut({ + DateTime? startTimestamp, + bool? sampled = true, + OnFinishedCallback? finishedCallback, + Duration? autoFinishAfter, + }) { + tracer = SentryTracer(context, hub, autoFinishAfter: autoFinishAfter); + + return SentrySpan( + tracer, + context, + hub, + startTimestamp: startTimestamp, + samplingDecision: SentryTracesSamplingDecision(sampled!), + finishedCallback: finishedCallback, + ); + } +} diff --git a/packages/dart/test/sentry_template_string_test.dart b/packages/dart/test/sentry_template_string_test.dart new file mode 100644 index 0000000000..0897c25ea5 --- /dev/null +++ b/packages/dart/test/sentry_template_string_test.dart @@ -0,0 +1,153 @@ +import 'package:sentry/src/sentry_template_string.dart'; +import 'package:test/test.dart'; + +void main() { + final fixture = Fixture(); + + group('SentryTemplateString', () { + test('basic string replacement', () { + final sut = fixture.getSut("Hello, %s!", ['John']); + final result = sut.format(); + + expect(result, 'Hello, John!'); + }); + + test('multiple string replacements', () { + final sut = + fixture.getSut("Hello, %s! You are %s years old.", ['John', '25']); + final result = sut.format(); + + expect(result, 'Hello, John! You are 25 years old.'); + }); + + test('bool argument', () { + final sut = fixture.getSut("The value is %s", [true]); + final result = sut.format(); + + expect(result, 'The value is true'); + }); + + test('int argument', () { + final sut = fixture.getSut("The number is %s", [42]); + final result = sut.format(); + + expect(result, 'The number is 42'); + }); + + test('double argument', () { + final sut = fixture.getSut("The decimal is %s", [3.14]); + final result = sut.format(); + + expect(result, 'The decimal is 3.14'); + }); + + test('mixed argument types', () { + final sut = fixture.getSut("Name: %s, Age: %s, Active: %s, Score: %s", + ['Alice', 30, true, 95.5]); + final result = sut.format(); + + expect(result, 'Name: Alice, Age: 30, Active: true, Score: 95.5'); + }); + + test('not enough arguments - replace with empty string', () { + final sut = fixture.getSut("Hello, %s! You are %s years old.", ['John']); + final result = sut.format(); + + expect(result, 'Hello, John! You are years old.'); + }); + + test('empty arguments trigger assertion error', () { + final sut = fixture.getSut("Hello, %s! You are %s years old.", []); + + expect(() => sut.format(), throwsA(isA())); + }); + + test('no placeholder strings trigger assertion error', () { + final sut = fixture.getSut("Hello, World!", ['ignored']); + + expect(() => sut.format(), throwsA(isA())); + }); + + test('too many arguments - ignore extras', () { + final sut = fixture.getSut("Hello, %s!", ['John', 'extra', 'arguments']); + final result = sut.format(); + + expect(result, 'Hello, John!'); + }); + + test('unsupported type with toString()', () { + final sut = + fixture.getSut("The object is %s", [CustomObject('test value')]); + final result = sut.format(); + + expect(result, 'The object is CustomObject: test value'); + }); + + test('unsupported type with throwing toString() falls back to empty string', + () { + final sut = + fixture.getSut("The object is %s", [ThrowingToStringObject()]); + final result = sut.format(); + + expect(result, 'The object is '); + }); + + test('unsupported type with default toString()', () { + final sut = + fixture.getSut("The object is %s", [NoToStringMethodObject()]); + final result = sut.format(); + + expect(result, 'The object is Instance of \'NoToStringMethodObject\''); + }); + + test('template with escaped %%s', () { + final sut = fixture.getSut("The percentage is %s%%", [50]); + final result = sut.format(); + + expect(result, 'The percentage is 50%'); + }); + + test('template with literal % character', () { + final sut = fixture + .getSut("The percentage is 50%%, with no extra %s", ['values']); + final result = sut.format(); + + expect(result, 'The percentage is 50%, with no extra values'); + }); + + test('toString() prints formatted string', () { + final sut = fixture.getSut("Hello, %s!", ['John']); + final result = sut.toString(); + + expect(result, 'Hello, John!'); + }); + }); +} + +class Fixture { + SentryTemplateString getSut(String template, List arguments) { + return SentryTemplateString(template, arguments); + } +} + +class CustomObject { + final String value; + + CustomObject(this.value); + + @override + String toString() { + return 'CustomObject: $value'; + } +} + +class ThrowingToStringObject { + @override + String toString() { + throw Exception('toString() is broken'); + } +} + +class NoToStringMethodObject { + var foo = "bar"; +} diff --git a/packages/dart/test/sentry_test.dart b/packages/dart/test/sentry_test.dart new file mode 100644 index 0000000000..9230d71396 --- /dev/null +++ b/packages/dart/test/sentry_test.dart @@ -0,0 +1,683 @@ +// ignore_for_file: deprecated_member_use_from_same_package + +import 'dart:async'; +import 'dart:isolate'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/dart_exception_type_identifier.dart'; +import 'package:sentry/src/event_processor/deduplication_event_processor.dart'; +import 'package:sentry/src/logs_enricher_integration.dart'; +import 'package:sentry/src/feature_flags_integration.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; +import 'mocks/mock_integration.dart'; +import 'mocks/mock_runtime_checker.dart'; +import 'mocks/mock_sentry_client.dart'; +import 'test_utils.dart'; + +AppRunner appRunner = () {}; + +void main() { + group('Sentry capture methods', () { + var client = MockSentryClient(); + + var anException = Exception(); + late SentryEvent fakeEvent; + late SentryMessage fakeMessage; + + setUp(() async { + final options = defaultTestOptions(); + await Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + options.tracesSampleRate = 1.0; + }, + ); + anException = Exception('anException'); + fakeEvent = getFakeEvent(); + fakeMessage = getFakeMessage(); + client = MockSentryClient(); + Sentry.bindClient(client); + }); + + tearDown(() async { + await Sentry.close(); + }); + + test('should capture the event', () async { + await Sentry.captureEvent(fakeEvent); + + expect(client.captureEventCalls.length, 1); + expect(client.captureEventCalls.first.event, fakeEvent); + expect(client.captureEventCalls.first.scope, isNotNull); + }); + + test('should capture the feedback event', () async { + final fakeFeedback = SentryFeedback(message: 'message'); + await Sentry.captureFeedback(fakeFeedback); + + expect(client.captureFeedbackCalls.length, 1); + expect(client.captureFeedbackCalls.first.feedback, fakeFeedback); + expect(client.captureFeedbackCalls.first.scope, isNotNull); + }); + + test('should capture the event withScope', () async { + await Sentry.captureEvent( + fakeEvent, + withScope: (scope) { + scope.setUser(SentryUser(id: 'foo bar')); + }, + ); + + expect(client.captureEventCalls.length, 1); + expect(client.captureEventCalls.first.event, fakeEvent); + expect(client.captureEventCalls.first.scope?.user?.id, 'foo bar'); + }); + + test('should capture the feedback event withScope', () async { + final fakeFeedback = SentryFeedback(message: 'message'); + await Sentry.captureFeedback( + fakeFeedback, + withScope: (scope) { + scope.setUser(SentryUser(id: 'foo bar')); + }, + ); + + expect(client.captureFeedbackCalls.length, 1); + expect(client.captureFeedbackCalls.first.scope?.user?.id, 'foo bar'); + }); + + test('should not capture a null exception', () async { + await Sentry.captureException(null); + expect(client.captureEventCalls.length, 0); + }); + + test('should capture the exception', () async { + await Sentry.captureException(anException); + expect(client.captureEventCalls.length, 1); + expect(client.captureEventCalls.first.event.throwable, anException); + expect(client.captureEventCalls.first.stackTrace, isNull); + expect(client.captureEventCalls.first.scope, isNotNull); + }); + + test('should capture exception with message', () async { + await Sentry.captureException(anException, + message: SentryMessage('Sentry rocks')); + + expect(client.captureEventCalls.first.event.message?.formatted, + 'Sentry rocks'); + }); + + test('should capture exception withScope', () async { + await Sentry.captureException(anException, withScope: (scope) { + scope.setUser(SentryUser(id: 'foo bar')); + }); + expect(client.captureEventCalls.length, 1); + expect(client.captureEventCalls.first.event.throwable, anException); + expect(client.captureEventCalls.first.scope?.user?.id, 'foo bar'); + }); + + test('should capture message', () async { + await Sentry.captureMessage( + fakeMessage.formatted, + level: SentryLevel.warning, + ); + + expect(client.captureMessageCalls.length, 1); + expect(client.captureMessageCalls.first.formatted, fakeMessage.formatted); + expect(client.captureMessageCalls.first.level, SentryLevel.warning); + }); + + test('should capture message withScope', () async { + await Sentry.captureMessage( + fakeMessage.formatted, + withScope: (scope) { + scope.setUser(SentryUser(id: 'foo bar')); + }, + ); + + expect(client.captureMessageCalls.length, 1); + expect(client.captureMessageCalls.first.formatted, fakeMessage.formatted); + expect(client.captureMessageCalls.first.scope?.user?.id, 'foo bar'); + }); + + test('should start transaction with given values', () async { + final tr = Sentry.startTransaction('name', 'op'); + await tr.finish(); + + expect(client.captureTransactionCalls.length, 1); + }); + + test('should start transaction with context', () async { + final tr = Sentry.startTransactionWithContext( + SentryTransactionContext('name', 'operation')); + await tr.finish(); + + expect(client.captureTransactionCalls.length, 1); + }); + + test('should start transaction with hint', () async { + final tr = Sentry.startTransactionWithContext( + SentryTransactionContext('name', 'operation')); + await tr.finish(); + + expect(client.captureTransactionCalls.length, 1); + }); + + test('should return span if bound to the scope', () async { + final tr = Sentry.startTransaction('name', 'op', bindToScope: true); + + expect(Sentry.getSpan(), tr); + }); + + test('should not return span if not bound to the scope', () async { + Sentry.startTransaction('name', 'op'); + + expect(Sentry.getSpan(), isNull); + }); + }); + + group('Sentry is enabled or disabled', () { + setUp(() async { + await Sentry.close(); + }); + + test('null DSN', () async { + final options = defaultTestOptions(); + expect( + () async => await Sentry.init( + options: options, + (options) => options.dsn = null, + ), + throwsArgumentError, + ); + expect(Sentry.isEnabled, false); + }); + + test('appRunner should be optional', () async { + expect(Sentry.isEnabled, false); + final options = defaultTestOptions(); + await Sentry.init( + options: options, + (options) => options.dsn = fakeDsn, + ); + expect(Sentry.isEnabled, true); + }); + + test('empty DSN', () async { + final options = defaultTestOptions(); + await Sentry.init( + options: options, + (options) => options.dsn = '', + ); + expect(Sentry.isEnabled, false); + }); + + test('empty DSN disables the SDK but runs the integrations', () async { + final integration = MockIntegration(); + + final options = defaultTestOptions(); + await Sentry.init( + options: options, + (options) { + options.dsn = ''; + options.addIntegration(integration); + }, + ); + + expect(integration.callCalls, 1); + }); + + test('close disables the SDK', () async { + final options = defaultTestOptions(); + await Sentry.init( + options: options, + (options) => options.dsn = fakeDsn, + ); + + Sentry.bindClient(MockSentryClient()); + + expect(Sentry.isEnabled, true); + + await Sentry.close(); + + expect(Sentry.isEnabled, false); + }); + }); + + group('Sentry init', () { + tearDown(() async { + await Sentry.close(); + }); + + test('should install integrations', () async { + final integration = MockIntegration(); + + final options = defaultTestOptions(); + await Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + options.addIntegration(integration); + }, + ); + + expect(integration.callCalls, 1); + }); + + test('should add default integrations', () async { + late SentryOptions optionsReference; + final options = defaultTestOptions(); + await Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + optionsReference = options; + }, + appRunner: appRunner, + ); + expect( + optionsReference.integrations + .whereType() + .length, + 1, + ); + expect( + optionsReference.integrations + .whereType() + .length, + 1, + ); + expect( + optionsReference.integrations + .whereType() + .length, + 1, + ); + }, onPlatform: {'browser': Skip()}); + + test('should add logsEnricherIntegration', () async { + late SentryOptions optionsReference; + final options = defaultTestOptions(); + + await Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + optionsReference = options; + }, + appRunner: appRunner, + ); + + expect( + optionsReference.integrations + .whereType() + .length, + 1, + ); + }); + + test('should add only web compatible default integrations', () async { + final options = defaultTestOptions(); + await Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + expect( + options.integrations.whereType().length, + 0, + ); + }, + ); + }, onPlatform: {'vm': Skip()}); + + test('should add feature flag FeatureFlagsIntegration', () async { + await Sentry.init( + options: defaultTestOptions(), + (options) => options.dsn = fakeDsn, + ); + + await Sentry.addFeatureFlag('foo', true); + + expect( + Sentry.currentHub.scope.contexts[SentryFeatureFlags.type]?.values.first + .flag, + equals('foo'), + ); + expect( + Sentry.currentHub.scope.contexts[SentryFeatureFlags.type]?.values.first + .result, + equals(true), + ); + }); + + test('addFeatureFlag should ignore non-boolean values', () async { + await Sentry.init( + options: defaultTestOptions(), + (options) => options.dsn = fakeDsn, + ); + + await Sentry.addFeatureFlag('foo1', 'some string'); + await Sentry.addFeatureFlag('foo2', 123); + await Sentry.addFeatureFlag('foo3', 1.23); + + final featureFlagsContext = + Sentry.currentHub.scope.contexts[SentryFeatureFlags.type]; + expect(featureFlagsContext, isNull); + }); + + test('should close integrations', () async { + final integration = MockIntegration(); + + final options = defaultTestOptions(); + await Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + options.addIntegration(integration); + }, + ); + + await Sentry.close(); + + expect(integration.callCalls, 1); + expect(integration.closeCalls, 1); + }); + + test('$DeduplicationEventProcessor is added on init', () async { + final options = defaultTestOptions(); + await Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + final count = options.eventProcessors + .whereType() + .length; + expect(count, 1); + }, + ); + }); + + test('should complete when appRunner completes', () async { + final completer = Completer(); + var completed = false; + + final options = defaultTestOptions(); + final init = Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + }, + appRunner: () => completer.future, + ).whenComplete(() => completed = true); + + await Future(() { + // We make the expectation only after all microtasks have completed, + // that Sentry.init might have scheduled. + expect(completed, false); + }); + + completer.complete(); + await init; + + expect(completed, true); + }); + + test('should add DartExceptionTypeIdentifier by default', () async { + final options = defaultTestOptions(); + await Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + }, + ); + + expect(options.exceptionTypeIdentifiers.length, 1); + final cachingIdentifier = options.exceptionTypeIdentifiers.first + as CachingExceptionTypeIdentifier; + expect( + cachingIdentifier, + isA().having( + (c) => c.identifier, + 'wrapped identifier', + isA(), + ), + ); + }); + + test('should set options.debug to true when in debug mode', () async { + final options = defaultTestOptions(); + options.runtimeChecker = MockRuntimeChecker(isDebug: true); + + expect(options.debug, isFalse); + await Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + }, + ); + expect(options.debug, isTrue); + }); + + test('should respect user options.debug when in debug mode', () async { + final options = defaultTestOptions(); + options.runtimeChecker = MockRuntimeChecker(isDebug: true); + + expect(options.debug, isFalse); + await Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + options.debug = false; + }, + ); + expect(options.debug, isFalse); + }); + + test('should leave options.debug unchanged when not in debug mode', + () async { + final options = defaultTestOptions(); + options.runtimeChecker = MockRuntimeChecker(isDebug: false); + + expect(options.debug, isFalse); + await Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + }, + ); + expect(options.debug, isFalse); + }); + + test('isolate completes when closing sentry', () async { + final onExit = ReceivePort(); + + Future _runSentry(String message) async { + await Sentry.init((options) { + options + ..dsn = fakeDsn + ..tracesSampleRate = 1.0; + }); + await Sentry.close(); + } + + final completer = Completer(); + + await Isolate.spawn( + _runSentry, + 'test', + onExit: onExit.sendPort, + ); + + var completed = false; + onExit.listen((message) { + completed = true; + completer.complete(); + }); + + await completer.future; + expect(completed, true); + }); + }, testOn: 'vm'); + + test('should complete when appRunner is not called in runZonedGuarded', + () async { + final completer = Completer(); + var completed = false; + + final options = defaultTestOptions(); + final init = Sentry.init( + options: options, + (options) { + options.dsn = fakeDsn; + }, + appRunner: () => completer.future, + callAppRunnerInRunZonedGuarded: false, + ).whenComplete(() => completed = true); + + await Future(() { + // We make the expectation only after all microtasks have completed, + // that Sentry.init might have scheduled. + expect(completed, false); + }); + + completer.complete(); + await init; + + expect(completed, true); + }); + + test('options.environment debug', () async { + final sentryOptions = + defaultTestOptions(checker: MockRuntimeChecker(isDebug: true)); + await Sentry.init( + (options) { + options.dsn = fakeDsn; + expect(options.environment, 'debug'); + expect(options.debug, true); + }, + options: sentryOptions, + ); + }); + + test('options.environment profile', () async { + final sentryOptions = + defaultTestOptions(checker: MockRuntimeChecker(isProfile: true)); + + await Sentry.init( + (options) { + options.dsn = fakeDsn; + expect(options.environment, 'profile'); + expect(options.debug, false); + }, + options: sentryOptions, + ); + }); + + test('options.environment production (defaultEnvironment)', () async { + final sentryOptions = + defaultTestOptions(checker: MockRuntimeChecker(isRelease: true)); + await Sentry.init( + (options) { + options.dsn = fakeDsn; + expect(options.environment, 'production'); + expect(options.debug, false); + }, + options: sentryOptions, + ); + }); + + test('options.logger is set by setting the debug flag', () async { + final sentryOptions = + defaultTestOptions(checker: MockRuntimeChecker(isDebug: true)); + + await Sentry.init( + (options) { + options.dsn = fakeDsn; + options.debug = true; + expect(options.diagnosticLog?.logger, isNot(noOpLog)); + + options.debug = false; + expect(options.diagnosticLog?.logger, noOpLog); + + options.debug = true; + expect(options.diagnosticLog?.logger, isNot(noOpLog)); + }, + options: sentryOptions, + ); + + expect(sentryOptions.diagnosticLog?.logger, isNot(noOpLog)); + }); + + group('Sentry init optionsConfiguration', () { + final fixture = Fixture(); + + tearDown(() async { + await Sentry.close(); + }); + + test('throw is handled and logged', () async { + // Use release mode in runtime checker to avoid additional log + final sentryOptions = + defaultTestOptions(checker: MockRuntimeChecker(isRelease: true)) + ..automatedTestMode = false + ..debug = true + ..log = fixture.mockLogger; + + final exception = Exception("Exception in options callback"); + await Sentry.init( + (options) async { + throw exception; + }, + options: sentryOptions, + ); + + expect(fixture.loggedException, exception); + expect(fixture.loggedLevel, SentryLevel.error); + }); + }); + + group('Sentry runZonedGuarded', () { + test('calling runZonedGuarded before init does not throw', () async { + await Sentry.close(); + + var expected = Exception("run zoned guarded exception"); + Object? actual; + + final completer = Completer(); + Sentry.runZonedGuarded(() { + throw expected; + }, (error, stackTrace) { + actual = error; + completer.complete(); + }); + + await completer.future; + + expect(actual, isNotNull); + expect(actual, expected); + }); + }); +} + +class Fixture { + bool logged = false; + SentryLevel? loggedLevel; + Object? loggedException; + + void mockLogger( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + if (!logged) { + logged = true; // Block multiple calls which override expected values. + loggedLevel = level; + loggedException = exception; + } + } +} diff --git a/packages/dart/test/sentry_trace_context_header_test.dart b/packages/dart/test/sentry_trace_context_header_test.dart new file mode 100644 index 0000000000..eb97f87913 --- /dev/null +++ b/packages/dart/test/sentry_trace_context_header_test.dart @@ -0,0 +1,72 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; + +void main() { + group('$SentryTraceContextHeader', () { + final traceId = SentryId.newId(); + + final context = SentryTraceContextHeader( + traceId, + '123', + release: 'release', + environment: 'environment', + userId: 'user_id', + transaction: 'transaction', + sampleRate: '1.0', + sampled: 'false', + replayId: SentryId.fromId('456'), + unknown: testUnknown, + ); + + final mapJson = { + 'trace_id': '$traceId', + 'public_key': '123', + 'release': 'release', + 'environment': 'environment', + 'user_id': 'user_id', + 'transaction': 'transaction', + 'sample_rate': '1.0', + 'sampled': 'false', + 'replay_id': '456', + }; + mapJson.addAll(testUnknown); + + test('fromJson', () { + expect(context.traceId.toString(), traceId.toString()); + expect(context.publicKey, '123'); + expect(context.release, 'release'); + expect(context.environment, 'environment'); + expect(context.userId, 'user_id'); + expect(context.transaction, 'transaction'); + expect(context.sampleRate, '1.0'); + expect(context.sampled, 'false'); + expect(context.replayId, SentryId.fromId('456')); + }); + + test('toJson', () { + final json = context.toJson(); + + expect(MapEquality().equals(json, mapJson), isTrue); + }); + + test('to baggage', () { + final baggage = context.toBaggage(); + + expect( + baggage.toHeaderString(), + 'sentry-trace_id=${traceId.toString()},' + 'sentry-public_key=123,' + 'sentry-release=release,' + 'sentry-environment=environment,' + 'sentry-user_id=user_id,' + 'sentry-transaction=transaction,' + 'sentry-sample_rate=1.0,' + 'sentry-sampled=false,' + 'sentry-replay_id=456', + ); + }); + }); +} diff --git a/packages/dart/test/sentry_trace_context_test.dart b/packages/dart/test/sentry_trace_context_test.dart new file mode 100644 index 0000000000..6f980b00d8 --- /dev/null +++ b/packages/dart/test/sentry_trace_context_test.dart @@ -0,0 +1,82 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/propagation_context.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; + +void main() { + final fixture = Fixture(); + + test('toJson serializes', () { + final sut = fixture.getSut(); + + final map = sut.toJson(); + + expect(map['span_id'], isNotNull); + expect(map['trace_id'], isNotNull); + expect(map['op'], 'op'); + expect(map['parent_span_id'], isNotNull); + expect(map['description'], 'desc'); + expect(map['status'], 'aborted'); + expect(map['origin'], 'auto.ui'); + expect(map['replay_id'], isNotNull); + expect(map['data'], {'key': 'value'}); + }); + + test('fromJson deserializes', () { + final map = { + 'op': 'op', + 'span_id': '0000000000000001', + 'trace_id': '00000000000000000000000000000002', + 'parent_span_id': '0000000000000003', + 'description': 'desc', + 'status': 'aborted', + 'origin': 'auto.ui', + 'replay_id': '00000000000000000000000000000004', + 'data': {'key': 'value'}, + }; + map.addAll(testUnknown); + final traceContext = SentryTraceContext.fromJson(map); + + expect(traceContext.description, 'desc'); + expect(traceContext.operation, 'op'); + expect(traceContext.spanId.toString(), '0000000000000001'); + expect(traceContext.traceId.toString(), '00000000000000000000000000000002'); + expect(traceContext.parentSpanId.toString(), '0000000000000003'); + expect(traceContext.status.toString(), 'aborted'); + expect(traceContext.sampled, true); + expect( + traceContext.replayId.toString(), '00000000000000000000000000000004'); + expect(traceContext.data, {'key': 'value'}); + }); + + test('fromPropagationContext creates valid SentryTraceContext', () { + final propagationContext = PropagationContext(); + + final traceContext1 = + SentryTraceContext.fromPropagationContext(propagationContext); + final traceContext2 = + SentryTraceContext.fromPropagationContext(propagationContext); + + expect(traceContext1.traceId, propagationContext.traceId); + expect(traceContext1.traceId, traceContext2.traceId); + // the span id is always generated new when creating a trace context from scope + expect(traceContext1.spanId, isNot(traceContext2.spanId)); + }); +} + +class Fixture { + SentryTraceContext getSut() { + return SentryTraceContext( + operation: 'op', + parentSpanId: SpanId.newId(), + description: 'desc', + sampled: true, + status: SpanStatus.aborted(), + origin: 'auto.ui', + replayId: SentryId.newId(), + data: {'key': 'value'}, + unknown: testUnknown, + ); + } +} diff --git a/packages/dart/test/sentry_tracer_test.dart b/packages/dart/test/sentry_tracer_test.dart new file mode 100644 index 0000000000..86f6f02ccb --- /dev/null +++ b/packages/dart/test/sentry_tracer_test.dart @@ -0,0 +1,637 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:test/test.dart'; + +import 'mocks/mock_hub.dart'; +import 'mocks/mock_sentry_client.dart'; +import 'test_utils.dart'; + +void main() { + group('$SentryTracer', () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + }); + + test('tracer sets name', () { + final sut = fixture.getSut(); + + expect(sut.name, 'name'); + }); + + test('tracer sets unsampled', () { + final sut = fixture.getSut(sampled: false); + + expect(sut.samplingDecision?.sampled, isFalse); + }); + + test('tracer sets sampled', () { + final sut = fixture.getSut(sampled: true); + + expect(sut.samplingDecision?.sampled, isTrue); + }); + + test('tracer origin from root span', () { + final sut = fixture.getSut(); + + expect(sut.origin, 'manual'); + }); + + test('tracer finishes with status', () async { + final sut = fixture.getSut(); + + await sut.finish(status: SpanStatus.aborted()); + + final tr = fixture.hub.captureTransactionCalls.first; + final trace = tr.transaction.contexts.trace; + + expect(trace?.status.toString(), 'aborted'); + }); + + test('tracer passes the trace context on finish', () async { + final sut = fixture.getSut(); + + await sut.finish(status: SpanStatus.aborted()); + + final tr = fixture.hub.captureTransactionCalls.first; + + expect(tr.traceContext, isNotNull); + }); + + test('tracer finishes with end timestamp', () async { + final sut = fixture.getSut(); + final endTimestamp = getUtcDateTime(); + + await sut.finish(endTimestamp: endTimestamp); + + expect(sut.endTimestamp, endTimestamp); + }); + + test('tracer does not finish unfinished spans', () async { + final sut = fixture.getSut(); + sut.startChild('child'); + + await sut.finish(status: SpanStatus.aborted()); + + final tr = fixture.hub.captureTransactionCalls.first; + final child = tr.transaction.spans.first; + + expect(child.status, isNull); + expect(child.endTimestamp, isNull); + }); + + test('tracer sets data to extra', () async { + final sut = fixture.getSut(); + + sut.setData('test', 'test'); + + await sut.finish(status: SpanStatus.aborted()); + + final tr = fixture.hub.captureTransactionCalls.first; + + // ignore: deprecated_member_use_from_same_package + expect(tr.transaction.extra?['test'], 'test'); + }); + + test('tracer removes data to extra', () async { + final sut = fixture.getSut(); + + sut.setData('test', 'test'); + sut.removeData('test'); + + await sut.finish(status: SpanStatus.aborted()); + + final tr = fixture.hub.captureTransactionCalls.first; + + // ignore: deprecated_member_use_from_same_package + expect(tr.transaction.extra?['test'], isNull); + }); + + test('tracer sets non-string data to extra', () async { + final sut = fixture.getSut(); + + sut.setData('test', {'key': 'value'}); + + await sut.finish(status: SpanStatus.aborted()); + + final tr = fixture.hub.captureTransactionCalls.first; + + // ignore: deprecated_member_use_from_same_package + expect(tr.transaction.extra?['test'], {'key': 'value'}); + }); + + test('tracer starts child', () async { + final sut = fixture.getSut(); + + final child = sut.startChild('operation', description: 'desc'); + await child.finish(); + + await sut.finish(status: SpanStatus.aborted()); + + final tr = fixture.hub.captureTransactionCalls.first; + final childSpan = tr.transaction.spans.first; + + expect(childSpan.context.description, 'desc'); + expect(childSpan.context.operation, 'operation'); + }); + + test('tracer starts child with parentSpanId', () async { + final sut = fixture.getSut(); + final parentId = SpanId.newId(); + final child = sut.startChildWithParentSpanId( + parentId, + 'op', + description: 'desc', + ); + await child.finish(); + + await sut.finish(status: SpanStatus.aborted()); + + final tr = fixture.hub.captureTransactionCalls.first; + final childSpan = tr.transaction.spans.first; + + expect(childSpan.context.description, 'desc'); + expect(childSpan.context.operation, 'op'); + expect(childSpan.context.parentSpanId.toString(), parentId.toString()); + }); + + test('tracer passes sampled decision to child', () async { + final sut = fixture.getSut(); + final parentId = SpanId.newId(); + final child = sut.startChildWithParentSpanId( + parentId, + 'op', + description: 'desc', + ); + await child.finish(); + + await sut.finish(status: SpanStatus.aborted()); + + expect(child.samplingDecision?.sampled, isTrue); + }); + + test('tracer passes unsampled decision to child', () async { + final sut = fixture.getSut(sampled: false); + final parentId = SpanId.newId(); + final child = sut.startChildWithParentSpanId( + parentId, + 'op', + description: 'desc', + ); + await child.finish(); + + await sut.finish(status: SpanStatus.aborted()); + + expect(child.samplingDecision?.sampled, isFalse); + }); + + test('toSentryTrace returns trace header', () { + final sut = fixture.getSut(); + + expect(sut.toSentryTrace().value, + '${sut.context.traceId}-${sut.context.spanId}-1'); + }); + + test('finish isnt allowed to be called twice', () async { + final sut = fixture.getSut(); + + await sut.finish(status: SpanStatus.ok()); + await sut.finish(status: SpanStatus.cancelled()); + + expect(sut.status, SpanStatus.ok()); + }); + + test('removeData isnt allowed to be called after finishing', () async { + final sut = fixture.getSut(); + + sut.setData('key', 'value'); + await sut.finish(status: SpanStatus.ok()); + sut.removeData('key'); + + expect(sut.data['key'], 'value'); + }); + + test('removeTag isnt allowed to be called after finishing', () async { + final sut = fixture.getSut(); + + sut.setTag('key', 'value'); + await sut.finish(status: SpanStatus.ok()); + sut.removeTag('key'); + + expect(sut.tags['key'], 'value'); + }); + + test('setData isnt allowed to be called after finishing', () async { + final sut = fixture.getSut(); + + sut.setData('key', 'value'); + await sut.finish(status: SpanStatus.ok()); + sut.setData('key', 'value2'); + + expect(sut.data['key'], 'value'); + }); + + test('setTag isnt allowed to be called after finishing', () async { + final sut = fixture.getSut(); + + sut.setTag('key', 'value'); + await sut.finish(status: SpanStatus.ok()); + sut.setTag('key', 'value2'); + + expect(sut.tags['key'], 'value'); + }); + + test('startChild isnt allowed to be called after finishing', () async { + final sut = fixture.getSut(); + + await sut.finish(status: SpanStatus.ok()); + final span = sut.startChild('op'); + + expect(NoOpSentrySpan(), span); + }); + + test('tracer finishes after auto finish duration', () async { + final sut = fixture.getSut(autoFinishAfter: Duration(milliseconds: 200)); + + expect(sut.finished, false); + await Future.delayed(Duration(milliseconds: 210)); + expect(sut.status, SpanStatus.ok()); + expect(sut.finished, true); + }); + + test('tracer reschedule finish timer', () async { + final sut = fixture.getSut(autoFinishAfter: Duration(milliseconds: 200)); + + final currentTimer = sut.autoFinishAfterTimer!; + + sut.scheduleFinish(); + + final newTimer = sut.autoFinishAfterTimer!; + + expect(currentTimer, isNot(equals(newTimer))); + }); + + test('tracer do not reschedule if finished', () async { + final sut = fixture.getSut(autoFinishAfter: Duration(milliseconds: 200)); + + final currentTimer = sut.autoFinishAfterTimer!; + + await sut.finish(); + + sut.scheduleFinish(); + + final newTimer = sut.autoFinishAfterTimer!; + + expect(currentTimer, newTimer); + }); + + test('tracer finish needs child to finish', () async { + final sut = fixture.getSut(waitForChildren: true); + + final child = sut.startChild('operation', description: 'description'); + + await sut.finish(); + expect(sut.finished, false); + + await child.finish(); + expect(sut.finished, true); + }); + + test('tracer finish needs all children to finish', () async { + final sut = fixture.getSut(waitForChildren: true); + + final childA = sut.startChild('operation-a', description: 'description'); + final childB = sut.startChild('operation-b', description: 'description'); + + await sut.finish(); + expect(sut.finished, false); + + await childA.finish(); + expect(sut.finished, false); + + await childB.finish(); + expect(sut.finished, true); + }); + + test( + 'tracer without finish will not be finished when children are finished', + () async { + final sut = fixture.getSut(waitForChildren: true); + + final childA = sut.startChild('operation-a', description: 'description'); + final childB = sut.startChild('operation-b', description: 'description'); + + await childA.finish(); + expect(sut.finished, false); + + await childB.finish(); + expect(sut.finished, false); + + await sut.finish(); + expect(sut.finished, true); + }); + + test('end trimmed to last child', () async { + final sut = fixture.getSut(trimEnd: true); + final endTimestamp = getUtcDateTime().add(Duration(minutes: 1)); + final olderEndTimeStamp = endTimestamp.add(Duration(seconds: 1)); + final oldestEndTimeStamp = olderEndTimeStamp.add(Duration(seconds: 1)); + + final childA = sut.startChild('operation-a', description: 'description'); + final childB = sut.startChild('operation-b', description: 'description'); + + await childA.finish(endTimestamp: endTimestamp); + await childB.finish(endTimestamp: olderEndTimeStamp); + await sut.finish(endTimestamp: oldestEndTimeStamp); + + expect(sut.endTimestamp, childB.endTimestamp); + }); + + test('end trimmed to child', () async { + final sut = fixture.getSut(trimEnd: true); + final endTimestamp = getUtcDateTime().add(Duration(minutes: 1)); + final olderEndTimeStamp = endTimestamp.add(Duration(seconds: 1)); + + final childA = sut.startChild('operation-a', description: 'description'); + + await childA.finish(endTimestamp: endTimestamp); + await sut.finish(endTimestamp: olderEndTimeStamp); + + expect(sut.endTimestamp, childA.endTimestamp); + }); + + test('end not trimmed when no child', () async { + final sut = fixture.getSut(trimEnd: true); + final endTimestamp = getUtcDateTime(); + + await sut.finish(endTimestamp: endTimestamp); + + expect(sut.endTimestamp, endTimestamp); + }); + + test('end trimmed to latest child end timestamp', () async { + final sut = fixture.getSut(trimEnd: true); + final rootEndInitial = getUtcDateTime(); + + final childAEnd = rootEndInitial; + final childBEnd = rootEndInitial.add(Duration(seconds: 1)); + final childCEnd = rootEndInitial; + + final childA = sut.startChild('operation-a', description: 'description'); + final childB = sut.startChild('operation-b', description: 'description'); + final childC = sut.startChild('operation-c', description: 'description'); + + await childA.finish(endTimestamp: childAEnd); + await childB.finish(endTimestamp: childBEnd); + await childC.finish(endTimestamp: childCEnd); + + await sut.finish(endTimestamp: rootEndInitial); + + expect(sut.endTimestamp, equals(childB.endTimestamp), + reason: + 'The root end timestamp should be updated to match the latest child end timestamp.'); + }); + + test('does not add more spans than configured in options', () async { + fixture.hub.options.maxSpans = 2; + final sut = fixture.getSut(); + + sut.startChild('child1'); + sut.startChild('child2'); + sut.startChild('child3'); + + expect(sut.children.length, 2); + }); + + test('when span limit is reached, startChild returns NoOpSpan', () async { + fixture.hub.options.maxSpans = 2; + final sut = fixture.getSut(); + + sut.startChild('child1'); + sut.startChild('child2'); + + expect(sut.startChild('child3'), isA()); + }); + + test('do not capture idle transaction without children', () async { + final sut = fixture.getSut(autoFinishAfter: Duration(milliseconds: 200)); + + await sut.finish(); + + expect(fixture.hub.captureTransactionCalls.isEmpty, true); + }); + + test('tracer sets measurement', () async { + final sut = fixture.getSut(); + + sut.setMeasurement('key', 1.0); + + expect(sut.measurements['key']!.value, 1.0); + + await sut.finish(); + }); + + test('tracer sets custom measurement unit', () async { + final sut = fixture.getSut(); + + sut.setMeasurement('key', 1.0, unit: DurationSentryMeasurementUnit.hour); + + expect(sut.measurements['key']!.value, 1.0); + expect(sut.measurements['key']?.unit, DurationSentryMeasurementUnit.hour); + + await sut.finish(); + }); + + test('tracer does not allow setting measurement if finished', () async { + final sut = fixture.getSut(); + + await sut.finish(); + + sut.setMeasurement('key', 1.0); + + expect(sut.measurements.isEmpty, true); + }); + + test('setMeasurement sets a measurement', () async { + final sut = fixture.getSut(); + sut.setMeasurement("test", 1); + expect(sut.measurements.containsKey("test"), true); + expect(sut.measurements["test"]!.value, 1); + }); + + test('setMeasurementFromChild does not override existing measurements', + () async { + final sut = fixture.getSut(); + sut.setMeasurement("test", 1); + sut.setMeasurementFromChild("test", 5); + expect(sut.measurements.containsKey("test"), true); + expect(sut.measurements["test"]!.value, 1); + }); + + test('hint passed to hub', () async { + final hint = Hint(); + + final sut = fixture.getSut(); + + await sut.finish(hint: hint); + + expect(fixture.hub.captureTransactionCalls.first.hint, hint); + }); + }); + + group('$SentryBaggageHeader', () { + late Fixture _fixture; + late Hub _hub; + + setUp(() async { + _fixture = Fixture(); + _hub = Hub(_fixture.options); + _hub.configureScope((scope) => scope.setUser(_fixture.user)); + + _hub.bindClient(_fixture.client); + }); + + SentryTracer getSut({SentryTracesSamplingDecision? samplingDecision}) { + final decision = samplingDecision ?? + SentryTracesSamplingDecision( + true, + sampleRate: 1.0, + sampleRand: 0.8, + ); + final _context = SentryTransactionContext( + 'name', + 'op', + transactionNameSource: SentryTransactionNameSource.custom, + samplingDecision: decision, + ); + + return SentryTracer(_context, _hub); + } + + test('returns baggage header', () { + final sut = getSut(); + final baggage = sut.toBaggageHeader(); + + expect(baggage!.name, 'baggage'); + + final newBaggage = SentryBaggage.fromHeader(baggage.value); + expect(newBaggage.get('sentry-trace_id'), sut.context.traceId.toString()); + expect(newBaggage.get('sentry-public_key'), 'public'); + expect(newBaggage.get('sentry-release'), 'release'); + expect(newBaggage.get('sentry-environment'), 'environment'); + expect(newBaggage.get('sentry-transaction'), 'name'); + expect(newBaggage.get('sentry-sample_rate'), '1'); + expect(newBaggage.getSampleRand(), 0.8); + expect(newBaggage.get('sentry-sampled'), 'true'); + }); + + test('skip transaction name if low cardinality', () { + final sut = getSut(); + sut.transactionNameSource = SentryTransactionNameSource.url; + final baggage = sut.toBaggageHeader(); + + final newBaggage = SentryBaggage.fromHeader(baggage!.value); + expect(newBaggage.get('sentry-transaction'), isNull); + }); + + test('sets transactionNameSource to source if not given', () { + final _context = SentryTransactionContext( + 'name', + 'op', + ); + + final tracer = SentryTracer(_context, _hub); + expect(tracer.transactionNameSource, SentryTransactionNameSource.custom); + }); + + test('formats the sample rate correctly', () { + final sut = getSut( + samplingDecision: SentryTracesSamplingDecision( + true, + sampleRate: 0.00000021, + )); + final baggage = sut.toBaggageHeader(); + + final newBaggage = SentryBaggage.fromHeader(baggage!.value); + expect(newBaggage.get('sentry-sample_rate'), '0.00000021'); + }); + }); + + group('$SentryTraceContextHeader', () { + late Fixture _fixture; + late Hub _hub; + + setUp(() async { + _fixture = Fixture(); + _hub = Hub(_fixture.options); + _hub.configureScope((scope) => scope.setUser(_fixture.user)); + + _hub.bindClient(_fixture.client); + }); + + SentryTracer getSut({SentryTracesSamplingDecision? samplingDecision}) { + final decision = samplingDecision ?? + SentryTracesSamplingDecision( + true, + sampleRate: 1.0, + ); + final _context = SentryTransactionContext( + 'name', + 'op', + transactionNameSource: SentryTransactionNameSource.custom, + samplingDecision: decision, + ); + + return SentryTracer(_context, _hub); + } + + test('returns trace context header', () { + final sut = getSut(); + final context = sut.traceContext(); + + expect(context!.traceId, sut.context.traceId); + expect(context.publicKey, 'public'); + expect(context.release, 'release'); + expect(context.environment, 'environment'); + expect(context.transaction, 'name'); + expect(context.sampleRate, '1'); + expect(context.sampled, 'true'); + }); + }); +} + +class Fixture { + final options = defaultTestOptions() + ..release = 'release' + ..environment = 'environment'; + + final client = MockSentryClient(); + + final user = SentryUser( + id: 'id', + ); + + final hub = MockHub(); + + SentryTracer getSut({ + bool? sampled = true, + bool waitForChildren = false, + bool trimEnd = false, + Duration? autoFinishAfter, + }) { + final context = SentryTransactionContext( + 'name', + 'op', + samplingDecision: SentryTracesSamplingDecision(sampled!), + origin: 'manual', + ); + return SentryTracer( + context, + hub, + waitForChildren: waitForChildren, + autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, + ); + } +} diff --git a/packages/dart/test/sentry_traces_sampler_test.dart b/packages/dart/test/sentry_traces_sampler_test.dart new file mode 100644 index 0000000000..cb2d96ecf1 --- /dev/null +++ b/packages/dart/test/sentry_traces_sampler_test.dart @@ -0,0 +1,150 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_traces_sampler.dart'; +import 'package:test/test.dart'; + +import 'test_utils.dart'; + +void main() { + late Fixture fixture; + const _sampleRand = 1.0; + + setUp(() { + fixture = Fixture(); + }); + + test('transactionContext has sampled', () { + final sut = fixture.getSut(); + + final trContext = SentryTransactionContext( + 'name', + 'op', + samplingDecision: SentryTracesSamplingDecision(true), + ); + final context = SentrySamplingContext(trContext, {}); + + expect(sut.sample(context, _sampleRand).sampled, true); + }); + + test('options has sampler', () { + double? sampler(SentrySamplingContext samplingContext) { + return 1.0; + } + + final sut = fixture.getSut( + tracesSampleRate: null, + tracesSampler: sampler, + ); + + final trContext = SentryTransactionContext( + 'name', + 'op', + ); + final context = SentrySamplingContext(trContext, {}); + + expect(sut.sample(context, _sampleRand).sampled, true); + }); + + test('transactionContext has parentSampled', () { + final sut = fixture.getSut(tracesSampleRate: null); + + final trContext = SentryTransactionContext( + 'name', + 'op', + parentSamplingDecision: SentryTracesSamplingDecision(true), + ); + final context = SentrySamplingContext(trContext, {}); + + expect(sut.sample(context, _sampleRand).sampled, true); + }); + + test('options has rate 1.0', () { + final sut = fixture.getSut(); + + final trContext = SentryTransactionContext( + 'name', + 'op', + ); + final context = SentrySamplingContext(trContext, {}); + + expect(sut.sample(context, _sampleRand).sampled, true); + }); + + test('options has rate 0.0', () { + final sut = fixture.getSut(tracesSampleRate: 0.0); + + final trContext = SentryTransactionContext( + 'name', + 'op', + ); + final context = SentrySamplingContext(trContext, {}); + + expect(sut.sample(context, _sampleRand).sampled, false); + }); + + test('does not sample if tracesSampleRate and tracesSampleRate are null', () { + final sut = fixture.getSut(tracesSampleRate: null, tracesSampler: null); + + final trContext = SentryTransactionContext( + 'name', + 'op', + ); + final context = SentrySamplingContext(trContext, {}); + final samplingDecision = sut.sample(context, _sampleRand); + + expect(samplingDecision.sampleRate, isNull); + expect(samplingDecision.sampleRand, isNull); + expect(samplingDecision.sampled, false); + }); + + test('tracesSampler exception is handled', () { + fixture.options.automatedTestMode = false; + final sut = fixture.getSut(debug: true); + + final exception = Exception("tracesSampler exception"); + double? sampler(SentrySamplingContext samplingContext) { + throw exception; + } + + fixture.options.tracesSampler = sampler; + + final trContext = SentryTransactionContext( + 'name', + 'op', + ); + final context = SentrySamplingContext(trContext, {}); + sut.sample(context, _sampleRand); + + expect(fixture.loggedException, exception); + expect(fixture.loggedLevel, SentryLevel.error); + }); +} + +class Fixture { + final options = defaultTestOptions(); + + SentryLevel? loggedLevel; + Object? loggedException; + + SentryTracesSampler getSut({ + double? tracesSampleRate = 1.0, + TracesSamplerCallback? tracesSampler, + bool debug = false, + }) { + options.tracesSampleRate = tracesSampleRate; + options.tracesSampler = tracesSampler; + options.debug = debug; + options.log = mockLogger; + return SentryTracesSampler(options); + } + + void mockLogger( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + loggedLevel = level; + loggedException = exception; + } +} diff --git a/packages/dart/test/sentry_transaction_context_test.dart b/packages/dart/test/sentry_transaction_context_test.dart new file mode 100644 index 0000000000..09330d0afd --- /dev/null +++ b/packages/dart/test/sentry_transaction_context_test.dart @@ -0,0 +1,76 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final _traceId = SentryId.newId(); + final _spanId = SpanId.newId(); + + SentryTransactionContext getSentryTransactionContext({ + bool? sampled, + SentryTransactionNameSource? transactionNameSource, + SentryBaggage? baggage, + }) { + final header = SentryTraceHeader(_traceId, _spanId, sampled: sampled); + return SentryTransactionContext.fromSentryTrace( + 'name', + 'operation', + header, + transactionNameSource: transactionNameSource, + baggage: baggage, + ); + } + + test('parent span id is set from header', () { + final context = getSentryTransactionContext(); + + expect(context.parentSpanId, _spanId); + }); + + test('trace id is set from header', () { + final context = getSentryTransactionContext(); + + expect(context.traceId, _traceId); + }); + + test('parent sampled is set from header', () { + final context = getSentryTransactionContext(sampled: true); + + expect(context.parentSamplingDecision?.sampled, true); + }); + + test('transactionNameSource is custom by default', () { + final context = getSentryTransactionContext(sampled: true); + + expect(context.transactionNameSource, SentryTransactionNameSource.custom); + }); + + test('transactionNameSource sets the given value', () { + final context = getSentryTransactionContext( + sampled: true, + transactionNameSource: SentryTransactionNameSource.component, + ); + + expect( + context.transactionNameSource, SentryTransactionNameSource.component); + }); + + test('sets sample rate if baggage is given', () { + final baggage = SentryBaggage({}); + final id = SentryId.newId().toString(); + baggage.setTraceId(id); + baggage.setPublicKey('publicKey'); + baggage.setSampleRate('1.0'); + final context = getSentryTransactionContext( + sampled: true, + baggage: baggage, + ); + + expect(context.parentSamplingDecision?.sampleRate, 1.0); + }); + + test('origin set to manual', () { + final context = getSentryTransactionContext(); + + expect(context.origin, SentryTraceOrigins.manual); + }); +} diff --git a/packages/dart/test/sentry_transaction_test.dart b/packages/dart/test/sentry_transaction_test.dart new file mode 100644 index 0000000000..14245aed5a --- /dev/null +++ b/packages/dart/test/sentry_transaction_test.dart @@ -0,0 +1,104 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:test/test.dart'; + +import 'mocks/mock_hub.dart'; +import 'test_utils.dart'; + +void main() { + final fixture = Fixture(); + + SentryTracer _createTracer({ + bool? sampled = true, + Hub? hub, + }) { + final context = SentryTransactionContext( + 'name', + 'op', + samplingDecision: SentryTracesSamplingDecision(sampled!), + transactionNameSource: SentryTransactionNameSource.component, + ); + return SentryTracer(context, hub ?? MockHub()); + } + + test('toJson serializes', () async { + final tracer = _createTracer(hub: fixture.hub); + + final child = tracer.startChild('child'); + await child.finish(); + await tracer.finish(); + + final sut = fixture.getSut(tracer); + final map = sut.toJson(); + + expect(map['type'], 'transaction'); + expect(map['start_timestamp'], isNotNull); + expect(map['spans'], isNotNull); + expect(map['transaction_info']['source'], 'component'); + }); + + test('returns finished if it is', () async { + final tracer = _createTracer(); + final child = tracer.startChild('child'); + await child.finish(); + await tracer.finish(); + + final sut = fixture.getSut(tracer); + + expect(sut.finished, true); + }); + + test('returns sampled if theres context', () async { + final tracer = _createTracer(sampled: true); + final child = tracer.startChild('child'); + await child.finish(); + await tracer.finish(); + + final sut = fixture.getSut(tracer); + + expect(sut.sampled, true); + }); + + test('returns contexts.trace.data if data is set', () async { + final tracer = _createTracer(sampled: true); + tracer.setData('key', 'value'); + final child = tracer.startChild('child'); + await child.finish(); + await tracer.finish(); + + final sut = fixture.getSut(tracer); + + expect(sut.contexts.trace!.data, {'key': 'value'}); + }); + + test('returns null contexts.trace.data if data is not set', () async { + final tracer = _createTracer(sampled: true); + final child = tracer.startChild('child'); + await child.finish(); + await tracer.finish(); + + final sut = fixture.getSut(tracer); + + expect(sut.contexts.trace!.data, isNull); + }); + + test('returns sampled false if not sampled', () async { + final tracer = _createTracer(sampled: false); + final child = tracer.startChild('child'); + await child.finish(); + await tracer.finish(); + + final sut = fixture.getSut(tracer); + + expect(sut.sampled, false); + }); +} + +class Fixture { + final SentryOptions options = defaultTestOptions(); + late final Hub hub = Hub(options); + + SentryTransaction getSut(SentryTracer tracer) { + return SentryTransaction(tracer); + } +} diff --git a/packages/dart/test/stack_trace_test.dart b/packages/dart/test/stack_trace_test.dart new file mode 100644 index 0000000000..ebb1b20613 --- /dev/null +++ b/packages/dart/test/stack_trace_test.dart @@ -0,0 +1,352 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:sentry/src/origin.dart'; +import 'package:sentry/src/platform/mock_platform.dart'; +import 'package:sentry/src/sentry_stack_trace_factory.dart'; +import 'package:stack_trace/stack_trace.dart'; +import 'package:test/test.dart'; + +import 'test_utils.dart'; + +void main() { + group('encodeStackTraceFrame', () { + test('marks dart: frames as not app frames', () { + final frame = Frame(Uri.parse('dart:core'), 1, 2, 'buzz'); + + expect( + Fixture().getSut().encodeStackTraceFrame(frame)!.toJson(), + { + 'abs_path': '${eventOrigin}dart:core', + 'function': 'buzz', + 'lineno': 1, + 'colno': 2, + 'in_app': false, + 'filename': 'core', + 'platform': 'dart', + }, + ); + }); + + test('cleans absolute paths', () { + final frame = Frame(Uri.parse('file://foo/bar/baz.dart'), 1, 2, 'buzz'); + expect( + Fixture().getSut().encodeStackTraceFrame(frame)!.toJson()['abs_path'], + '${eventOrigin}baz.dart', + ); + }); + + test('send exception package', () { + final frame = Frame(Uri.parse('package:toolkit/baz.dart'), 1, 2, 'buzz'); + final encodedFrame = Fixture() + .getSut(inAppExcludes: ['toolkit']).encodeStackTraceFrame(frame)!; + expect(encodedFrame.package, 'toolkit'); + }); + + test('apply inAppExcludes', () { + final frame = Frame(Uri.parse('package:toolkit/baz.dart'), 1, 2, 'buzz'); + final serializedFrame = Fixture() + .getSut(inAppExcludes: ['toolkit']).encodeStackTraceFrame(frame)!; + + expect(serializedFrame.inApp, false); + }); + + test('apply inAppIncludes', () { + final frame = Frame(Uri.parse('package:toolkit/baz.dart'), 1, 2, 'buzz'); + final serializedFrame = Fixture() + .getSut(inAppIncludes: ['toolkit']).encodeStackTraceFrame(frame)!; + + expect(serializedFrame.inApp, true); + }); + + test('flutter package is not inApp', () { + final frame = + Frame(Uri.parse('package:flutter/material.dart'), 1, 2, 'buzz'); + final serializedFrame = Fixture().getSut().encodeStackTraceFrame(frame)!; + + expect(serializedFrame.inApp, false); + }); + + test('apply inAppIncludes with precedence', () { + final frame = Frame(Uri.parse('package:toolkit/baz.dart'), 1, 2, 'buzz'); + final serializedFrame = Fixture().getSut( + inAppExcludes: ['toolkit'], + inAppIncludes: ['toolkit']).encodeStackTraceFrame(frame)!; + + expect(serializedFrame.inApp, true); + }); + + test('uses default value from options, default = true', () { + // The following frame meets the following conditions: + // - frame.uri.scheme is empty + // - frame.package is null + // These conditions triggers the default value being used + final frame = Frame.parseVM('#0 Foo (async/future.dart:0:0)'); + + // default is true + final serializedFrame = Fixture() + .getSut(considerInAppFramesByDefault: true) + .encodeStackTraceFrame(frame)!; + + expect(serializedFrame.inApp, true); + }); + + test('uses default value from options, default = false', () { + // The following frame meets the following conditions: + // - frame.uri.scheme is empty + // - frame.package is null + // These conditions triggers the default value being used + final frame = Frame.parseVM('#0 Foo (async/future.dart:0:0)'); + + // default is true + final serializedFrame = Fixture() + .getSut(considerInAppFramesByDefault: false) + .encodeStackTraceFrame(frame)!; + + expect(serializedFrame.inApp, false); + }); + + test('adds module for package frames', () { + final frame = Frame( + Uri.parse( + 'package:app_name/features/login/ui/view_model/login_view_model.dart'), + 1, + 2, + 'buzz', + ); + + final sentryStackFrame = Fixture() + .getSut(includeModuleInStackTrace: true) + .encodeStackTraceFrame(frame)!; + + expect(sentryStackFrame.module, 'app_name/features/login/ui/view_model'); + }); + + test( + 'does not add module for package frames when includeModuleInStackTrace is false', + () { + final frame = Frame( + Uri.parse( + 'package:app_name/features/login/ui/view_model/login_view_model.dart'), + 1, + 2, + 'buzz', + ); + + final sentryStackFrame = Fixture() + .getSut(includeModuleInStackTrace: false) + .encodeStackTraceFrame(frame)!; + + expect(sentryStackFrame.module, null); + }); + }); + + group('encodeStackTrace', () { + test('encodes a simple stack trace', () { + final frames = + Fixture().getSut(considerInAppFramesByDefault: true).parse(''' +#0 baz (file:///pathto/test.dart:50:3) +#1 bar (file:///pathto/test.dart:46:9) + ''').frames.map((frame) => frame.toJson()); + + expect(frames, [ + { + 'abs_path': '${eventOrigin}test.dart', + 'function': 'bar', + 'lineno': 46, + 'colno': 9, + 'in_app': true, + 'filename': 'test.dart', + 'platform': 'dart', + }, + { + 'abs_path': '${eventOrigin}test.dart', + 'function': 'baz', + 'lineno': 50, + 'colno': 3, + 'in_app': true, + 'filename': 'test.dart', + 'platform': 'dart', + }, + ]); + }); + + test('obsoleted getStackFrames works as expected', () { + final sut = Fixture().getSut(considerInAppFramesByDefault: true); + final trace = ''' +#0 baz (file:///pathto/test.dart:50:3) +#1 bar (file:///pathto/test.dart:46:9) + '''; + final frames1 = sut.parse(trace).frames.map((frame) => frame.toJson()); + // ignore: deprecated_member_use_from_same_package + final frames2 = sut.getStackFrames(trace).map((frame) => frame.toJson()); + + expect(frames1, equals(frames2)); + }); + + test('encodes an asynchronous stack trace', () { + final frames = + Fixture().getSut(considerInAppFramesByDefault: true).parse(''' +#0 baz (file:///pathto/test.dart:50:3) + +#1 bar (file:///pathto/test.dart:46:9) + ''').frames.map((frame) => frame.toJson()); + + expect(frames, [ + { + 'abs_path': '${eventOrigin}test.dart', + 'function': 'bar', + 'lineno': 46, + 'colno': 9, + 'in_app': true, + 'filename': 'test.dart', + 'platform': 'dart', + }, + { + 'abs_path': '', + }, + { + 'abs_path': '${eventOrigin}test.dart', + 'function': 'baz', + 'lineno': 50, + 'colno': 3, + 'in_app': true, + 'filename': 'test.dart', + 'platform': 'dart', + }, + ]); + }); + + test('parses obfuscated stack trace', () { + final stackTraces = [ + // Older format up to Dart SDK v2.18 (Flutter v3.3) + ''' +warning: This VM has been configured to produce stack traces that violate the Dart standard. +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** +pid: 30930, tid: 30990, name 1.ui +build_id: '5346e01103ffeed44e97094ff7bfcc19' +isolate_dso_base: 723d447000, vm_dso_base: 723d447000 +isolate_instructions: 723d452000, vm_instructions: 723d449000 + #00 abs 000000723d6346d7 virt 00000000001ed6d7 _kDartIsolateSnapshotInstructions+0x1e26d7 + #01 abs 000000723d637527 virt 00000000001f0527 _kDartIsolateSnapshotInstructions+0x1e5527 + ''', + // Newer format + ''' +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** +pid: 19226, tid: 6103134208, name io.flutter.ui +os: macos arch: arm64 comp: no sim: no +isolate_dso_base: 10fa20000, vm_dso_base: 10fa20000 +isolate_instructions: 10fa27070, vm_instructions: 10fa21e20 + #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7 + #01 abs 000000723d637527 _kDartIsolateSnapshotInstructions+0x1e5527 + ''', + ]; + + for (var traceString in stackTraces) { + final frames = Fixture() + .getSut(considerInAppFramesByDefault: true) + .parse(traceString) + .frames + .map((frame) => frame.toJson()); + + expect( + frames, + [ + { + 'platform': 'native', + 'instruction_addr': '0x000000723d637527', + }, + { + 'platform': 'native', + 'instruction_addr': '0x000000723d6346d7', + }, + ], + reason: "Failed to parse StackTrace:$traceString"); + } + }); + + test('parses normal stack trace', () { + final frames = + Fixture().getSut(considerInAppFramesByDefault: true).parse(''' +#0 asyncThrows (file:/foo/bar/main.dart:404) +#1 MainScaffold.build. (package:example/main.dart:131) +#2 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:341) + ''').frames.map((frame) => frame.toJson()); + expect(frames, [ + { + 'filename': 'platform_dispatcher.dart', + 'function': 'PlatformDispatcher._dispatchPointerDataPacket', + 'lineno': 341, + 'abs_path': '${eventOrigin}dart:ui/platform_dispatcher.dart', + 'in_app': false, + 'platform': 'dart', + }, + { + 'filename': 'main.dart', + 'package': 'example', + 'function': 'MainScaffold.build.', + 'lineno': 131, + 'abs_path': '${eventOrigin}package:example/main.dart', + 'in_app': true, + 'platform': 'dart', + }, + { + 'filename': 'main.dart', + 'function': 'asyncThrows', + 'lineno': 404, + 'abs_path': '${eventOrigin}main.dart', + 'in_app': true, + 'platform': 'dart', + } + ]); + }); + + test('remove frames if only async gap is left', () { + final frames = Fixture() + .getSut(considerInAppFramesByDefault: true) + .parse(StackTrace.fromString(''' + + ''')) + .frames + .map((frame) => frame.toJson()); + expect(frames.isEmpty, true); + }); + + test('sets platform to javascript for web and dart for non-web', () { + final frame = Frame(Uri.parse('file://foo/bar/baz.dart'), 1, 2, 'buzz'); + final fixture = Fixture(); + + // Test for web platform + fixture.options.platform = MockPlatform(isWeb: true); + final webSut = fixture.getSut(); + var webFrame = webSut.encodeStackTraceFrame(frame)!; + expect(webFrame.platform, 'javascript'); + + // Test for non-web platform + fixture.options.platform = MockPlatform(isWeb: false); + final nativeFrameBeforeSut = fixture.getSut(); + var nativeFrameBefore = + nativeFrameBeforeSut.encodeStackTraceFrame(frame)!; + expect(nativeFrameBefore.platform, 'dart'); + }); + }); +} + +class Fixture { + final options = defaultTestOptions()..platform = MockPlatform(isWeb: false); + + SentryStackTraceFactory getSut({ + List inAppIncludes = const [], + List inAppExcludes = const [], + bool considerInAppFramesByDefault = true, + bool includeModuleInStackTrace = false, + }) { + inAppIncludes.forEach(options.addInAppInclude); + inAppExcludes.forEach(options.addInAppExclude); + options.considerInAppFramesByDefault = considerInAppFramesByDefault; + options.includeModuleInStackTrace = includeModuleInStackTrace; + + return SentryStackTraceFactory(options); + } +} diff --git a/packages/dart/test/test_utils.dart b/packages/dart/test/test_utils.dart new file mode 100644 index 0000000000..cb9fc9a479 --- /dev/null +++ b/packages/dart/test/test_utils.dart @@ -0,0 +1,459 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/platform/platform.dart'; +import 'package:sentry/src/version.dart'; +import 'package:test/test.dart'; + +const String testDsn = 'https://public:secret@sentry.example.com/1'; +const String _testDsnWithoutSecret = 'https://public@sentry.example.com/1'; +const String _testDsnWithPath = + 'https://public:secret@sentry.example.com/path/1'; +const String _testDsnWithPort = + 'https://public:secret@sentry.example.com:8888/1'; + +SentryOptions defaultTestOptions( + {Platform? platform, RuntimeChecker? checker}) { + return SentryOptions(dsn: testDsn, platform: platform, checker: checker) + ..automatedTestMode = true; +} + +void testHeaders( + Map? headers, + ClockProvider fakeClockProvider, { + String? sdkName, + bool withUserAgent = true, + bool compressPayload = true, + bool withSecret = true, +}) { + final expectedHeaders = { + 'Content-Type': 'application/x-sentry-envelope', + 'X-Sentry-Auth': 'Sentry sentry_version=7, ' + 'sentry_client=$sdkName/$sdkVersion, ' + 'sentry_key=public' + }; + + if (withSecret) { + expectedHeaders['X-Sentry-Auth'] = + '${expectedHeaders['X-Sentry-Auth']!}, sentry_secret=secret'; + } + + if (withUserAgent) { + expectedHeaders['User-Agent'] = '$sdkName/$sdkVersion'; + } + + if (compressPayload) { + expectedHeaders['Content-Encoding'] = 'gzip'; + } + + expect(headers, expectedHeaders); +} + +Future testCaptureException( + bool compressPayload, + Codec, List?>? gzip, + bool isWeb, +) async { + final fakeClockProvider = () => DateTime.utc(2017, 1, 2); + + Uri? postUri; + Map? headers; + List? body; + final httpMock = MockClient((http.Request request) async { + if (request.method == 'POST') { + postUri = request.url; + headers = request.headers; + body = request.bodyBytes; + return http.Response('{"id": "test-event-id"}', 200); + } + fail('Unexpected request on ${request.method} ${request.url} in HttpMock'); + }); + + final options = defaultTestOptions() + ..compressPayload = compressPayload + ..clock = fakeClockProvider + ..httpClient = httpMock + ..serverName = 'test.server.com' + ..release = '1.2.3' + ..environment = 'staging'; + + var sentryId = SentryId.empty(); + final client = SentryClient(options); + + try { + throw ArgumentError('Test error'); + } catch (error, stackTrace) { + sentryId = await client.captureException(error, stackTrace: stackTrace); + expect('$sentryId', 'testeventid'); + } + + final dsn = Dsn.parse(options.dsn!); + expect(postUri, dsn.postUri); + + testHeaders( + headers, + fakeClockProvider, + compressPayload: compressPayload, + withUserAgent: !isWeb, + sdkName: sdkName(isWeb), + ); + + String envelopeData; + if (compressPayload) { + envelopeData = utf8.decode(gzip!.decode(body)); + } else { + envelopeData = utf8.decode(body!); + } + final eventJson = envelopeData.split('\n').last; + final data = json.decode(eventJson) as Map?; + + // so we assert the generated and returned id + data!['event_id'] = sentryId.toString(); + + final stacktrace = data['exception']['values'].first['stacktrace']; + + expect(stacktrace['frames'], const TypeMatcher()); + expect(stacktrace['frames'], isNotEmpty); + + final topFrame = + (stacktrace['frames'] as Iterable).last as Map; + if (topFrame['function'].contains('browser_test.dart.wasm')) { + // TODO stacktrace parsing for wasm is not implemented yet + // {filename: unparsed, function: at testCaptureException (http://localhost:59959/9R3KYfjvkWCySr4h2hI0pVO7PqmPFeE6/test/sentry_browser_test.dart.browser_test.dart.wasm:wasm-function[1007]:0x4bc18), abs_path: http://localhost:59959/unparsed, in_app: true} + return; + } + expect( + topFrame.keys, + [ + 'filename', + 'function', + 'lineno', + 'colno', + 'abs_path', + 'in_app', + 'platform' + ], + ); + + if (isWeb) { + // can't test the full url + // the localhost port can change + final absPathUri = Uri.parse(topFrame['abs_path'] as String); + expect(absPathUri.host, 'localhost'); + expect( + absPathUri.path, + anyOf([ + '/sentry_browser_test.dart.browser_test.dart.js', + '/sentry_browser_test.dart.browser_test.dart.wasm' + ])); + + expect( + topFrame['filename'], + anyOf([ + 'sentry_browser_test.dart.browser_test.dart.js', + 'sentry_browser_test.dart.browser_test.dart.wasm' + ])); + expect( + topFrame['function'], + anyOf([ + 'Object.wrapException', + 'testCaptureException', + 'module0.testCaptureException' + ])); + + expect(data['event_id'], sentryId.toString()); + expect(data['timestamp'], '2017-01-02T00:00:00.000Z'); + expect(data['platform'], 'javascript'); + expect(data['sdk'], { + 'version': sdkVersion, + 'name': sdkName(isWeb), + 'packages': [ + {'name': 'pub:sentry', 'version': sdkVersion} + ] + }); + expect(data['server_name'], 'test.server.com'); + expect(data['release'], '1.2.3'); + expect(data['environment'], 'staging'); + + expect(data['exception']['values'].first['type'], 'ArgumentError'); + expect(data['exception']['values'].first['value'], + 'Invalid argument(s): Test error'); + } else { + expect(topFrame['abs_path'], 'test_utils.dart'); + expect(topFrame['filename'], 'test_utils.dart'); + expect(topFrame['function'], 'testCaptureException'); + + expect(data['event_id'], sentryId.toString()); + expect(data['timestamp'], '2017-01-02T00:00:00.000Z'); + expect(data['platform'], 'other'); + expect(data['sdk'], { + 'version': sdkVersion, + 'name': 'sentry.dart', + 'packages': [ + {'name': 'pub:sentry', 'version': sdkVersion} + ] + }); + expect(data['server_name'], 'test.server.com'); + expect(data['release'], '1.2.3'); + expect(data['environment'], 'staging'); + expect(data['exception']['values'].first['type'], 'ArgumentError'); + expect(data['exception']['values'].first['value'], + 'Invalid argument(s): Test error'); + } + + expect(topFrame['lineno'], greaterThan(0)); + expect(topFrame['in_app'], true); + + client.close(); +} + +void runTest({Codec, List?>? gzip, bool isWeb = false}) { + test('can parse DSN', () async { + final options = defaultTestOptions(); + final client = SentryClient(options); + + final dsn = Dsn.parse(options.dsn!); + + expect(dsn.uri, Uri.parse(testDsn)); + expect( + dsn.postUri, + Uri.parse('https://sentry.example.com/api/1/envelope/'), + ); + expect(dsn.publicKey, 'public'); + expect(dsn.secretKey, 'secret'); + expect(dsn.projectId, '1'); + client.close(); + }); + + test('can parse DSN without secret', () async { + final options = defaultTestOptions()..dsn = _testDsnWithoutSecret; + final client = SentryClient(options); + + final dsn = Dsn.parse(options.dsn!); + + expect(dsn.uri, Uri.parse(_testDsnWithoutSecret)); + expect( + dsn.postUri, + Uri.parse('https://sentry.example.com/api/1/envelope/'), + ); + expect(dsn.publicKey, 'public'); + expect(dsn.secretKey, null); + expect(dsn.projectId, '1'); + client.close(); + }); + + test('can parse DSN with path', () async { + final options = defaultTestOptions()..dsn = _testDsnWithPath; + final client = SentryClient(options); + + final dsn = Dsn.parse(options.dsn!); + + expect(dsn.uri, Uri.parse(_testDsnWithPath)); + expect( + dsn.postUri, + Uri.parse('https://sentry.example.com/path/api/1/envelope/'), + ); + expect(dsn.publicKey, 'public'); + expect(dsn.secretKey, 'secret'); + expect(dsn.projectId, '1'); + client.close(); + }); + test('can parse DSN with port', () async { + final options = defaultTestOptions()..dsn = _testDsnWithPort; + final client = SentryClient(options); + + final dsn = Dsn.parse(options.dsn!); + + expect(dsn.uri, Uri.parse(_testDsnWithPort)); + expect( + dsn.postUri, + Uri.parse('https://sentry.example.com:8888/api/1/envelope/'), + ); + expect(dsn.publicKey, 'public'); + expect(dsn.secretKey, 'secret'); + expect(dsn.projectId, '1'); + client.close(); + }); + test('sends client auth header without secret', () async { + final fakeClockProvider = () => DateTime.utc(2017, 1, 2); + + Map? headers; + + final httpMock = MockClient((http.Request request) async { + if (request.method == 'POST') { + headers = request.headers; + return http.Response('{"id": "testeventid"}', 200); + } + fail( + 'Unexpected request on ${request.method} ${request.url} in HttpMock', + ); + }); + + final client = SentryClient( + defaultTestOptions() + ..dsn = _testDsnWithoutSecret + ..httpClient = httpMock + ..clock = fakeClockProvider + ..compressPayload = false + ..serverName = 'test.server.com' + ..release = '1.2.3' + ..environment = 'staging', + ); + + try { + throw ArgumentError('Test error'); + } catch (error, stackTrace) { + final sentryId = + await client.captureException(error, stackTrace: stackTrace); + expect('$sentryId', 'testeventid'); + } + + testHeaders( + headers, + fakeClockProvider, + withUserAgent: !isWeb, + compressPayload: false, + withSecret: false, + sdkName: sdkName(isWeb), + ); + + client.close(); + }); + + test('sends an exception report (compressed)', () async { + await testCaptureException(true, gzip, isWeb); + }, onPlatform: { + 'browser': const Skip(), + }); + + test('sends an exception report (uncompressed)', () async { + await testCaptureException(false, gzip, isWeb); + }); + + test('reads error message from the x-sentry-error header', () async { + final fakeClockProvider = () => DateTime.utc(2017, 1, 2); + + final httpMock = MockClient((http.Request request) async { + if (request.method == 'POST') { + return http.Response('', 401, headers: { + 'x-sentry-error': 'Invalid api key', + }); + } + fail( + 'Unexpected request on ${request.method} ${request.url} in HttpMock', + ); + }); + + final client = SentryClient( + defaultTestOptions() + ..httpClient = httpMock + ..clock = fakeClockProvider + ..compressPayload = false + ..serverName = 'test.server.com' + ..release = '1.2.3' + ..environment = 'staging', + ); + + try { + throw ArgumentError('Test error'); + } catch (error, stackTrace) { + final sentryId = + await client.captureException(error, stackTrace: stackTrace); + expect('$sentryId', '00000000000000000000000000000000'); + } + + client.close(); + }); + + test('$SentryEvent user overrides client', () async { + final fakeClockProvider = () => DateTime.utc(2017, 1, 2); + + String? loggedUserId; // used to find out what user context was sent + final httpMock = MockClient((http.Request request) async { + if (request.method == 'POST') { + final bodyData = request.bodyBytes; + final decoded = const Utf8Codec().decode(bodyData); + final eventJson = decoded.split('\n').last; + final dynamic decodedJson = json.decode(eventJson); + + loggedUserId = decodedJson['user']['id'] as String?; + return http.Response( + '', + 401, + headers: { + 'x-sentry-error': 'Invalid api key', + }, + ); + } + fail( + 'Unexpected request on ${request.method} ${request.url} in HttpMock', + ); + }); + + final clientUser = SentryUser( + id: 'client_user', + username: 'username', + email: 'email@email.com', + ipAddress: '127.0.0.1', + ); + final eventUser = SentryUser( + id: 'event_user', + username: 'username', + email: 'email@email.com', + ipAddress: '127.0.0.1', + data: {'foo': 'bar'}, + ); + + final options = defaultTestOptions() + ..httpClient = httpMock + ..clock = fakeClockProvider + ..compressPayload = false + ..serverName = 'test.server.com' + ..release = '1.2.3' + ..environment = 'staging' + ..sendClientReports = false; + + final client = SentryClient(options); + + try { + throw ArgumentError('Test error'); + } catch (error) { + final eventWithoutContext = SentryEvent( + eventId: SentryId.empty(), + throwable: error, + ); + final eventWithContext = SentryEvent( + eventId: SentryId.empty(), + throwable: error, + user: eventUser, + ); + + final scope = Scope(options); + await scope.setUser(clientUser); + + await client.captureEvent( + eventWithoutContext, + scope: scope, + ); + expect(loggedUserId, clientUser.id); + + final secondScope = Scope(options); + await secondScope.setUser(clientUser); + + await client.captureEvent( + eventWithContext, + scope: secondScope, + ); + expect(loggedUserId, eventUser.id); + } + + client.close(); + }); +} diff --git a/packages/dart/test/transport/client_report_transport_test.dart b/packages/dart/test/transport/client_report_transport_test.dart new file mode 100644 index 0000000000..df9447c768 --- /dev/null +++ b/packages/dart/test/transport/client_report_transport_test.dart @@ -0,0 +1,169 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/client_reports/client_report.dart'; +import 'package:sentry/src/client_reports/discard_reason.dart'; +import 'package:sentry/src/client_reports/discarded_event.dart'; +import 'package:sentry/src/sentry_item_type.dart'; +import 'package:sentry/src/transport/client_report_transport.dart'; +import 'package:sentry/src/transport/data_category.dart'; +import 'package:sentry/src/transport/rate_limiter.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; +import '../mocks/mock_client_report_recorder.dart'; +import '../mocks/mock_envelope.dart'; +import '../mocks/mock_transport.dart'; +import '../test_utils.dart'; + +void main() { + group('filter', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('filter called', () async { + final mockRateLimiter = MockRateLimiter(); + final sut = fixture.getSut(rateLimiter: mockRateLimiter); + + final envelope = MockEnvelope(); + envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; + + await sut.send(envelope); + + expect(mockRateLimiter.envelopeToFilter, envelope); + expect(fixture.mockTransport.envelopes.first, envelope); + }); + + test('send nothing when filtered event null', () async { + final mockRateLimiter = MockRateLimiter()..filterReturnsNull = true; + final sut = fixture.getSut(rateLimiter: mockRateLimiter); + + final envelope = MockEnvelope(); + envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; + + final eventId = await sut.send(envelope); + + expect(eventId, SentryId.empty()); + expect(fixture.mockTransport.called(0), true); + }); + }); + + group('client reports', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('send calls flush', () async { + final sut = fixture.getSut(); + + final envelope = MockEnvelope(); + envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; + + await sut.send(envelope); + + expect(fixture.recorder.flushCalled, true); + }); + + test('send adds client report', () async { + final clientReport = ClientReport( + DateTime(0), + [DiscardedEvent(DiscardReason.rateLimitBackoff, DataCategory.error, 1)], + ); + fixture.recorder.clientReport = clientReport; + + final sut = fixture.getSut(); + + final envelope = MockEnvelope(); + envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; + + await sut.send(envelope); + + expect(envelope.clientReport, clientReport); + }); + }); + + group('client report only event', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('send after filtering out 10 times and client report', () async { + final clientReport = ClientReport( + DateTime(0), + [DiscardedEvent(DiscardReason.rateLimitBackoff, DataCategory.error, 1)], + ); + fixture.recorder.clientReport = clientReport; + + final mockRateLimiter = MockRateLimiter()..filterReturnsNull = true; + + final sut = fixture.getSut(rateLimiter: mockRateLimiter); + + final envelope = MockEnvelope(); + envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; + + for (int i = 0; i < 10; i++) { + await sut.send(envelope); + } + + expect(fixture.mockTransport.called(1), true); + + final sentEnvelope = fixture.mockTransport.envelopes.first; + expect(sentEnvelope.items.length, 1); + expect(sentEnvelope.items[0].header.type, SentryItemType.clientReport); + }); + + test('filter out after 10 times with no client reports', () async { + final mockRateLimiter = MockRateLimiter()..filterReturnsNull = true; + + final sut = fixture.getSut(rateLimiter: mockRateLimiter); + + final envelope = MockEnvelope(); + envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; + + for (int i = 0; i < 10; i++) { + await sut.send(envelope); + } + + expect(fixture.mockTransport.called(0), true); + }); + + test('reset counter', () async { + final clientReport = ClientReport( + DateTime(0), + [DiscardedEvent(DiscardReason.rateLimitBackoff, DataCategory.error, 1)], + ); + fixture.recorder.clientReport = clientReport; + + final mockRateLimiter = MockRateLimiter()..filterReturnsNull = true; + + final sut = fixture.getSut(rateLimiter: mockRateLimiter); + + final envelope = MockEnvelope(); + envelope.items = [SentryEnvelopeItem.fromEvent(SentryEvent())]; + + for (int i = 0; i < 20; i++) { + await sut.send(envelope); + } + + expect(fixture.mockTransport.called(2), true); + }); + }); +} + +class Fixture { + final options = defaultTestOptions(); + + late var recorder = MockClientReportRecorder(); + late var mockTransport = MockTransport(); + + ClientReportTransport getSut({RateLimiter? rateLimiter}) { + mockTransport.parseFromEnvelope = false; + options.recorder = recorder; + return ClientReportTransport(rateLimiter, options, mockTransport); + } +} diff --git a/packages/dart/test/transport/http_transport_test.dart b/packages/dart/test/transport/http_transport_test.dart new file mode 100644 index 0000000000..0c0307a4b5 --- /dev/null +++ b/packages/dart/test/transport/http_transport_test.dart @@ -0,0 +1,306 @@ +import 'package:collection/collection.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/client_reports/discard_reason.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:sentry/src/transport/data_category.dart'; +import 'package:sentry/src/transport/http_transport.dart'; +import 'package:sentry/src/transport/rate_limiter.dart'; +import 'package:test/test.dart'; + +import '../mocks.dart'; +import '../mocks/mock_client_report_recorder.dart'; +import '../mocks/mock_hub.dart'; +import '../test_utils.dart'; + +void main() { + group('send', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('event with http client', () async { + List? body; + + final httpMock = MockClient((http.Request request) async { + body = request.bodyBytes; + return http.Response('{}', 200); + }); + + fixture.options.compressPayload = false; + final mockRateLimiter = MockRateLimiter(); + + final sut = fixture.getSut(httpMock, mockRateLimiter); + + final sentryEvent = SentryEvent(); + final envelope = SentryEnvelope.fromEvent( + sentryEvent, + fixture.options.sdk, + dsn: fixture.options.dsn, + ); + await sut.send(envelope); + + final envelopeData = []; + await envelope + .envelopeStream(fixture.options) + .forEach(envelopeData.addAll); + + expect(body, envelopeData); + }); + }); + + group('updateRetryAfterLimits', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('retryAfterHeader', () async { + final httpMock = MockClient((http.Request request) async { + return http.Response('{}', 429, headers: {'Retry-After': '1'}); + }); + final mockRateLimiter = MockRateLimiter(); + final sut = fixture.getSut(httpMock, mockRateLimiter); + + final sentryEvent = SentryEvent(); + final envelope = SentryEnvelope.fromEvent( + sentryEvent, + fixture.options.sdk, + dsn: fixture.options.dsn, + ); + + mockRateLimiter.filter(envelope); + + await sut.send(envelope); + + expect(mockRateLimiter.envelopeToFilter?.header.eventId, + sentryEvent.eventId); + + expect(mockRateLimiter.errorCode, 429); + expect(mockRateLimiter.retryAfterHeader, '1'); + expect(mockRateLimiter.sentryRateLimitHeader, isNull); + + expect(fixture.loggedLevel, SentryLevel.warning); + expect( + fixture.loggedMessage, 'Rate limit reached, failed to send envelope'); + }); + + test('sentryRateLimitHeader', () async { + final httpMock = MockClient((http.Request request) async { + return http.Response('{}', 200, + headers: {'X-Sentry-Rate-Limits': 'fixture-sentryRateLimitHeader'}); + }); + final mockRateLimiter = MockRateLimiter(); + final sut = fixture.getSut(httpMock, mockRateLimiter); + + final sentryEvent = SentryEvent(); + final envelope = SentryEnvelope.fromEvent( + sentryEvent, + fixture.options.sdk, + dsn: fixture.options.dsn, + ); + await sut.send(envelope); + + expect(mockRateLimiter.errorCode, 200); + expect(mockRateLimiter.retryAfterHeader, isNull); + expect(mockRateLimiter.sentryRateLimitHeader, + 'fixture-sentryRateLimitHeader'); + }); + }); + + group('sent_at', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('capture envelope sets sent_at in header', () async { + final sentryEvent = SentryEvent(); + final envelope = SentryEnvelope.fromEvent( + sentryEvent, + fixture.options.sdk, + dsn: fixture.options.dsn, + ); + + final httpMock = MockClient((http.Request request) async { + return http.Response('{}', 200); + }); + final sut = fixture.getSut(httpMock, MockRateLimiter()); + await sut.send(envelope); + + expect(envelope.header.sentAt, DateTime.utc(2019)); + }); + }); + + group('client reports', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('does records lost event for error >= 400', () async { + final httpMock = MockClient((http.Request request) async { + return http.Response('{}', 400); + }); + final sut = fixture.getSut(httpMock, MockRateLimiter()); + + final sentryEvent = SentryEvent(); + final envelope = SentryEnvelope.fromEvent( + sentryEvent, + fixture.options.sdk, + dsn: fixture.options.dsn, + ); + await sut.send(envelope); + + expect(fixture.clientReportRecorder.discardedEvents.first.reason, + DiscardReason.networkError); + expect(fixture.clientReportRecorder.discardedEvents.first.category, + DataCategory.error); + }); + + test('does records lost transaction and span for error >= 400', () async { + final httpMock = MockClient((http.Request request) async { + return http.Response('{}', 400); + }); + final sut = fixture.getSut(httpMock, MockRateLimiter()); + + final transaction = fixture.getTransaction(); + transaction.tracer.startChild('child1'); + transaction.tracer.startChild('child2'); + final envelope = SentryEnvelope.fromTransaction( + transaction, + fixture.options.sdk, + dsn: fixture.options.dsn, + ); + await sut.send(envelope); + + final transactionDiscardedEvent = fixture + .clientReportRecorder.discardedEvents + .firstWhereOrNull((element) => + element.category == DataCategory.transaction && + element.reason == DiscardReason.networkError); + + final spanDiscardedEvent = fixture.clientReportRecorder.discardedEvents + .firstWhereOrNull((element) => + element.category == DataCategory.span && + element.reason == DiscardReason.networkError); + + expect(transactionDiscardedEvent, isNotNull); + expect(spanDiscardedEvent, isNotNull); + expect(spanDiscardedEvent!.quantity, 3); + }); + + test('does record lost feedback for error >= 400', () async { + final httpMock = MockClient((http.Request request) async { + return http.Response('{}', 400); + }); + final sut = fixture.getSut(httpMock, MockRateLimiter()); + + final feedback = SentryFeedback(message: 'fixture-message'); + final feedbackEvent = SentryEvent( + type: 'feedback', + contexts: Contexts(feedback: feedback), + level: SentryLevel.info, + ); + final envelope = SentryEnvelope.fromEvent( + feedbackEvent, + fixture.options.sdk, + dsn: fixture.options.dsn, + ); + await sut.send(envelope); + + expect(fixture.clientReportRecorder.discardedEvents.first.reason, + DiscardReason.networkError); + expect(fixture.clientReportRecorder.discardedEvents.first.category, + DataCategory.feedback); + }); + + test('does not record lost event for error 429', () async { + final httpMock = MockClient((http.Request request) async { + return http.Response('{}', 429); + }); + final sut = fixture.getSut(httpMock, MockRateLimiter()); + + final sentryEvent = SentryEvent(); + final envelope = SentryEnvelope.fromEvent( + sentryEvent, + fixture.options.sdk, + dsn: fixture.options.dsn, + ); + await sut.send(envelope); + + expect(fixture.clientReportRecorder.discardedEvents.isEmpty, isTrue); + + expect(fixture.loggedLevel, SentryLevel.warning); + expect( + fixture.loggedMessage, 'Rate limit reached, failed to send envelope'); + }); + + test('does record lost event for error >= 500', () async { + final httpMock = MockClient((http.Request request) async { + return http.Response('{}', 500); + }); + final sut = fixture.getSut(httpMock, MockRateLimiter()); + + final sentryEvent = SentryEvent(); + final envelope = SentryEnvelope.fromEvent( + sentryEvent, + fixture.options.sdk, + dsn: fixture.options.dsn, + ); + await sut.send(envelope); + + expect(fixture.clientReportRecorder.discardedEvents.first.reason, + DiscardReason.networkError); + expect(fixture.clientReportRecorder.discardedEvents.first.category, + DataCategory.error); + }); + }); +} + +class Fixture { + final options = defaultTestOptions(); + + late var clientReportRecorder = MockClientReportRecorder(); + + HttpTransport getSut(http.Client client, RateLimiter rateLimiter) { + options.debug = true; + options.log = mockLogger; + options.httpClient = client; + options.recorder = clientReportRecorder; + options.clock = () { + return DateTime.utc(2019); + }; + return HttpTransport(options, rateLimiter); + } + + SentryTransaction getTransaction() { + final context = SentryTransactionContext( + 'name', + 'op', + samplingDecision: SentryTracesSamplingDecision(true), + ); + final tracer = SentryTracer(context, MockHub()); + return SentryTransaction(tracer); + } + + SentryLevel? loggedLevel; + String? loggedMessage; + + void mockLogger( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + loggedLevel = level; + loggedMessage = message; + } +} diff --git a/packages/dart/test/transport/spotlight_http_transport_test.dart b/packages/dart/test/transport/spotlight_http_transport_test.dart new file mode 100644 index 0000000000..3e9d742bb1 --- /dev/null +++ b/packages/dart/test/transport/spotlight_http_transport_test.dart @@ -0,0 +1,69 @@ +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/transport/http_transport.dart'; +import 'package:sentry/src/transport/rate_limiter.dart'; +import 'package:sentry/src/transport/spotlight_http_transport.dart'; +import 'package:test/expect.dart'; +import 'package:test/scaffolding.dart'; + +import '../mocks.dart'; +import '../mocks/mock_client_report_recorder.dart'; +import '../test_utils.dart'; + +void main() { + group('send to Sentry', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('send event to Sentry even if Spotlight fails', () async { + List? body; + + final httpMock = MockClient((http.Request request) async { + body = request.bodyBytes; + if (request.url.toString() == fixture.options.spotlight.url) { + return http.Response('{}', 500); + } + return http.Response('{}', 200); + }); + + fixture.options.compressPayload = false; + final mockRateLimiter = MockRateLimiter(); + final sut = fixture.getSut(httpMock, mockRateLimiter); + + final sentryEvent = SentryEvent(); + final envelope = SentryEnvelope.fromEvent( + sentryEvent, + fixture.options.sdk, + dsn: fixture.options.dsn, + ); + await sut.send(envelope); + + final envelopeData = []; + await envelope + .envelopeStream(fixture.options) + .forEach(envelopeData.addAll); + + expect(body, envelopeData); + }); + }); +} + +class Fixture { + final options = defaultTestOptions(); + + late var clientReportRecorder = MockClientReportRecorder(); + + Transport getSut(http.Client client, RateLimiter rateLimiter) { + options.httpClient = client; + options.recorder = clientReportRecorder; + options.clock = () { + return DateTime.utc(2019); + }; + final httpTransport = HttpTransport(options, rateLimiter); + return SpotlightHttpTransport(options, httpTransport); + } +} diff --git a/packages/dart/test/transport/tesk_queue_test.dart b/packages/dart/test/transport/tesk_queue_test.dart new file mode 100644 index 0000000000..dca3243f01 --- /dev/null +++ b/packages/dart/test/transport/tesk_queue_test.dart @@ -0,0 +1,145 @@ +import 'dart:async'; + +import 'package:sentry/src/client_reports/discard_reason.dart'; +import 'package:sentry/src/transport/data_category.dart'; +import 'package:sentry/src/transport/task_queue.dart'; +import 'package:test/test.dart'; + +import '../mocks/mock_client_report_recorder.dart'; +import '../test_utils.dart'; + +void main() { + group("called sync", () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test("enqueue only executed `maxQueueSize` times when not awaiting", + () async { + final sut = fixture.getSut(maxQueueSize: 5); + + var completedTasks = 0; + + for (int i = 0; i < 10; i++) { + unawaited(sut.enqueue(() async { + print('Task $i'); + await Future.delayed(Duration(milliseconds: 1)); + completedTasks += 1; + return 1 + 1; + }, -1, DataCategory.error)); + } + + // This will always await the other futures, even if they are running longer, as it was scheduled after them. + print('Started waiting for first 5 tasks'); + await Future.delayed(Duration(milliseconds: 1)); + print('Stopped waiting for first 5 tasks'); + + expect(completedTasks, 5); + }); + + test("enqueue picks up tasks again after await in-between", () async { + final sut = fixture.getSut(maxQueueSize: 5); + + var completedTasks = 0; + + for (int i = 1; i <= 10; i++) { + unawaited(sut.enqueue(() async { + print('Started task $i'); + await Future.delayed(Duration(milliseconds: 1)); + print('Completed task $i'); + completedTasks += 1; + return 1 + 1; + }, -1, DataCategory.error)); + } + + print('Started waiting for first 5 tasks'); + await Future.delayed(Duration(milliseconds: 1)); + print('Stopped waiting for first 5 tasks'); + + for (int i = 6; i <= 15; i++) { + unawaited(sut.enqueue(() async { + print('Started task $i'); + await Future.delayed(Duration(milliseconds: 1)); + print('Completed task $i'); + completedTasks += 1; + return 1 + 1; + }, -1, DataCategory.error)); + } + + print('Started waiting for second 5 tasks'); + await Future.delayed(Duration(milliseconds: 5)); + print('Stopped waiting for second 5 tasks'); + + expect(completedTasks, 10); // 10 were dropped + }); + + test("enqueue executes all tasks when awaiting", () async { + final sut = fixture.getSut(maxQueueSize: 5); + + var completedTasks = 0; + + for (int i = 0; i < 10; i++) { + await sut.enqueue(() async { + print('Task $i'); + await Future.delayed(Duration(milliseconds: 1)); + completedTasks += 1; + return 1 + 1; + }, -1, DataCategory.error); + } + expect(completedTasks, 10); + }); + + test("throwing tasks still execute as expected", () async { + final sut = fixture.getSut(maxQueueSize: 5); + + var completedTasks = 0; + + for (int i = 0; i < 10; i++) { + try { + await sut.enqueue(() async { + completedTasks += 1; + throw Error(); + }, -1, DataCategory.error); + } catch (_) { + // Ignore + } + } + expect(completedTasks, 10); + }); + + test('recording dropped event when category set', () async { + final sut = fixture.getSut(maxQueueSize: 5); + + for (int i = 0; i < 10; i++) { + unawaited(sut.enqueue(() async { + print('Task $i'); + return 1 + 1; + }, -1, DataCategory.error)); + } + + // This will always await the other futures, even if they are running longer, as it was scheduled after them. + print('Started waiting for first 5 tasks'); + await Future.delayed(Duration(milliseconds: 1)); + print('Stopped waiting for first 5 tasks'); + + expect(fixture.clientReportRecorder.discardedEvents.length, 5); + for (final event in fixture.clientReportRecorder.discardedEvents) { + expect(event.reason, DiscardReason.queueOverflow); + expect(event.category, DataCategory.error); + expect(event.quantity, 1); + } + }); + }); +} + +class Fixture { + final options = defaultTestOptions(); + + late var clientReportRecorder = MockClientReportRecorder(); + + TaskQueue getSut({required int maxQueueSize}) { + return DefaultTaskQueue(maxQueueSize, options.log, clientReportRecorder); + } +} diff --git a/packages/dart/test/unsupported_throwables_test.dart b/packages/dart/test/unsupported_throwables_test.dart new file mode 100644 index 0000000000..9cac063a34 --- /dev/null +++ b/packages/dart/test/unsupported_throwables_test.dart @@ -0,0 +1,92 @@ +import 'package:sentry/src/hub.dart'; +import 'package:test/expect.dart'; +import 'package:test/scaffolding.dart'; + +void main() { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + group('unsupported throwable types', () { + test('wrapped string throwable does not throw when expanding', () async { + final throwableHandler = fixture.sut; + final unsupportedThrowable = 'test throwable'; + final wrappedThrowable = + throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); + + expect(() { + fixture.expando[wrappedThrowable]; + }, returnsNormally); + }); + + test('wrapped int throwable does not throw when expanding', () async { + final throwableHandler = fixture.sut; + final unsupportedThrowable = 1; + final wrappedThrowable = + throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); + + expect(() { + fixture.expando[wrappedThrowable]; + }, returnsNormally); + }); + + test('wrapped double throwable does not throw when expanding', () async { + final throwableHandler = fixture.sut; + final unsupportedThrowable = 1.0; + final wrappedThrowable = + throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); + + expect(() { + fixture.expando[wrappedThrowable]; + }, returnsNormally); + }); + + test('wrapped bool throwable does not throw when expanding', () async { + final throwableHandler = fixture.sut; + final unsupportedThrowable = true; + final wrappedThrowable = + throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); + + expect(() { + fixture.expando[wrappedThrowable]; + }, returnsNormally); + }); + + test( + 'creating multiple instances of string wrapped exceptions accesses the same expando value', + () async { + final unsupportedThrowable = 'test throwable'; + final throwableHandler = fixture.sut; + + final first = + throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); + fixture.expando[first] = 1; + + final second = + throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); + expect(fixture.expando[second], 1); + fixture.expando[second] = 2.0; + + final third = + throwableHandler.wrapIfUnsupportedType(unsupportedThrowable); + expect(fixture.expando[third], 2.0); + }); + }); + + group('supported throwable type', () { + test('does not wrap exception if it is a supported type', () async { + final supportedThrowable = Exception('test throwable'); + final result = fixture.sut.wrapIfUnsupportedType(supportedThrowable); + + expect(result, supportedThrowable); + }); + }); +} + +class Fixture { + final expando = Expando(); + + UnsupportedThrowablesHandler get sut => UnsupportedThrowablesHandler(); +} diff --git a/packages/dart/test/utils/http_header_utils_test.dart b/packages/dart/test/utils/http_header_utils_test.dart new file mode 100644 index 0000000000..21405677ff --- /dev/null +++ b/packages/dart/test/utils/http_header_utils_test.dart @@ -0,0 +1,18 @@ +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + test('get content length lower case', () { + final headers = { + 'content-length': ['12'] + }; + expect(HttpHeaderUtils.getContentLength(headers), 12); + }); + + test('get content length camel case', () { + final headers = { + 'Content-Length': ['12'] + }; + expect(HttpHeaderUtils.getContentLength(headers), 12); + }); +} diff --git a/packages/dart/test/utils/http_sanitizer_test.dart b/packages/dart/test/utils/http_sanitizer_test.dart new file mode 100644 index 0000000000..2a4e0a58be --- /dev/null +++ b/packages/dart/test/utils/http_sanitizer_test.dart @@ -0,0 +1,189 @@ +import 'package:sentry/src/utils/http_sanitizer.dart'; +import 'package:test/test.dart'; + +void main() { + test('returns null for null', () { + expect(HttpSanitizer.sanitizeUrl(null), isNull); + }); + + test('strips user info with user and password from http', () { + final sanitizedUri = HttpSanitizer.sanitizeUrl( + "http://user:password@sentry.io?q=1&s=2&token=secret#top"); + expect(sanitizedUri?.url, "http://[Filtered]:[Filtered]@sentry.io"); + expect(sanitizedUri?.query, "q=1&s=2&token=secret"); + expect(sanitizedUri?.fragment, "top"); + }); + + test('strips user info with user and password from https', () { + final sanitizedUri = HttpSanitizer.sanitizeUrl( + "https://user:password@sentry.io?q=1&s=2&token=secret#top"); + expect(sanitizedUri?.url, "https://[Filtered]:[Filtered]@sentry.io"); + expect(sanitizedUri?.query, "q=1&s=2&token=secret"); + expect(sanitizedUri?.fragment, "top"); + }); + + test('splits url', () { + final sanitizedUri = + HttpSanitizer.sanitizeUrl("https://sentry.io?q=1&s=2&token=secret#top"); + expect(sanitizedUri?.url, "https://sentry.io"); + expect(sanitizedUri?.query, "q=1&s=2&token=secret"); + expect(sanitizedUri?.fragment, "top"); + }); + + test('splits relative url', () { + final sanitizedUri = + HttpSanitizer.sanitizeUrl("/users/1?q=1&s=2&token=secret#top"); + expect(sanitizedUri?.url, "/users/1"); + expect(sanitizedUri?.query, "q=1&s=2&token=secret"); + expect(sanitizedUri?.fragment, "top"); + }); + + test('splits relative root url', () { + final sanitizedUri = + HttpSanitizer.sanitizeUrl("/?q=1&s=2&token=secret#top"); + expect(sanitizedUri?.url, "/"); + expect(sanitizedUri?.query, "q=1&s=2&token=secret"); + expect(sanitizedUri?.fragment, "top"); + }); + + test('splits url with just query and fragment', () { + final sanitizedUri = + HttpSanitizer.sanitizeUrl("/?q=1&s=2&token=secret#top"); + expect(sanitizedUri?.url, "/"); + expect(sanitizedUri?.query, "q=1&s=2&token=secret"); + expect(sanitizedUri?.fragment, "top"); + }); + + test('splits relative url with query only', () { + final sanitizedUri = + HttpSanitizer.sanitizeUrl("/users/1?q=1&s=2&token=secret"); + expect(sanitizedUri?.url, "/users/1"); + expect(sanitizedUri?.query, "q=1&s=2&token=secret"); + expect(sanitizedUri?.fragment, isNull); + }); + + test('splits relative url with fragment only', () { + final sanitizedUri = HttpSanitizer.sanitizeUrl("/users/1#top"); + expect(sanitizedUri?.url, "/users/1"); + expect(sanitizedUri?.query, isNull); + expect(sanitizedUri?.fragment, "top"); + }); + + test('strips user info with user and password without query', () { + final sanitizedUri = + HttpSanitizer.sanitizeUrl("https://user:password@sentry.io#top"); + expect(sanitizedUri?.url, "https://[Filtered]:[Filtered]@sentry.io"); + expect(sanitizedUri?.query, isNull); + expect(sanitizedUri?.fragment, "top"); + }); + + test('splits without query', () { + final sanitizedUri = HttpSanitizer.sanitizeUrl("https://sentry.io#top"); + expect(sanitizedUri?.url, "https://sentry.io"); + expect(sanitizedUri?.query, isNull); + expect(sanitizedUri?.fragment, "top"); + }); + + test('strips user info with user and password without fragment', () { + final sanitizedUri = HttpSanitizer.sanitizeUrl( + "https://user:password@sentry.io?q=1&s=2&token=secret"); + expect(sanitizedUri?.url, "https://[Filtered]:[Filtered]@sentry.io"); + expect(sanitizedUri?.query, "q=1&s=2&token=secret"); + expect(sanitizedUri?.fragment, isNull); + }); + + test('strips user info with user and password without query or fragment', () { + final sanitizedUri = + HttpSanitizer.sanitizeUrl("https://user:password@sentry.io"); + expect(sanitizedUri?.url, "https://[Filtered]:[Filtered]@sentry.io"); + expect(sanitizedUri?.query, isNull); + expect(sanitizedUri?.fragment, isNull); + }); + + test('splits url without query or fragment and no authority', () { + final sanitizedUri = HttpSanitizer.sanitizeUrl("https://sentry.io"); + expect(sanitizedUri?.url, "https://sentry.io"); + expect(sanitizedUri?.query, isNull); + expect(sanitizedUri?.fragment, isNull); + }); + + test('strips user info with user only', () { + final sanitizedUri = HttpSanitizer.sanitizeUrl( + "https://user@sentry.io?q=1&s=2&token=secret#top"); + expect(sanitizedUri?.url, "https://[Filtered]@sentry.io"); + expect(sanitizedUri?.query, "q=1&s=2&token=secret"); + expect(sanitizedUri?.fragment, "top"); + }); + + test('no details extracted with query after fragment', () { + final sanitizedUri = HttpSanitizer.sanitizeUrl( + "https://user:password@sentry.io#fragment?q=1&s=2&token=secret"); + expect(sanitizedUri?.url, isNull); + expect(sanitizedUri?.query, isNull); + expect(sanitizedUri?.fragment, isNull); + }); + + test('no details extracted with query after fragment without authority', () { + final sanitizedUri = HttpSanitizer.sanitizeUrl( + "https://sentry.io#fragment?q=1&s=2&token=secret"); + expect(sanitizedUri?.url, isNull); + expect(sanitizedUri?.query, isNull); + expect(sanitizedUri?.fragment, isNull); + }); + + test('no details extracted from malformed url', () { + final sanitizedUri = HttpSanitizer.sanitizeUrl( + "htps://user@sentry.io#fragment?q=1&s=2&token=secret"); + expect(sanitizedUri?.url, isNull); + expect(sanitizedUri?.query, isNull); + expect(sanitizedUri?.fragment, isNull); + }); + + test('removes security headers', () { + final securityHeaders = [ + "X-FORWARDED-FOR", + "AUTHORIZATION", + "COOKIE", + "SET-COOKIE", + "X-API-KEY", + "X-REAL-IP", + "REMOTE-ADDR", + "FORWARDED", + "PROXY-AUTHORIZATION", + "X-CSRF-TOKEN", + "X-CSRFTOKEN", + "X-XSRF-TOKEN" + ]; + + final headers = {}; + for (final securityHeader in securityHeaders) { + headers[securityHeader] = 'foo'; + headers[securityHeader.toLowerCase()] = 'bar'; + headers[securityHeader._capitalize()] = 'baz'; + } + final sanitizedHeaders = HttpSanitizer.sanitizedHeaders(headers); + expect(sanitizedHeaders, isNotNull); + expect(sanitizedHeaders?.isEmpty, true); + }); + + test('handle throwing uri', () { + final details = HttpSanitizer.sanitizeUrl('::Not valid URI::'); + expect(details, isNull); + }); + + test('keeps email address', () { + final urlDetails = HttpSanitizer.sanitizeUrl( + "https://staging.server.com/api/v4/auth/password/reset/email@example.com"); + expect( + "https://staging.server.com/api/v4/auth/password/reset/email@example.com", + urlDetails?.url); + expect(urlDetails?.query, isNull); + expect(urlDetails?.fragment, isNull); + }); +} + +extension _StringExtension on String { + String _capitalize() { + return "${this[0].toUpperCase()}${substring(1).toLowerCase()}"; + } +} diff --git a/packages/dart/test/utils/regex_utils_test.dart b/packages/dart/test/utils/regex_utils_test.dart new file mode 100644 index 0000000000..ff098ab964 --- /dev/null +++ b/packages/dart/test/utils/regex_utils_test.dart @@ -0,0 +1,24 @@ +import 'package:sentry/src/utils/regex_utils.dart'; +import 'package:test/test.dart'; + +void main() { + group('regex_utils', () { + final testString = "this is a test"; + + test('testString contains string pattern', () { + expect(isMatchingRegexPattern(testString, ["is"]), isTrue); + }); + + test('testString does not contain string pattern', () { + expect(isMatchingRegexPattern(testString, ["not"]), isFalse); + }); + + test('testString contains regex pattern', () { + expect(isMatchingRegexPattern(testString, ["^this.*\$"]), isTrue); + }); + + test('testString does not contain regex pattern', () { + expect(isMatchingRegexPattern(testString, ["^is.*\$"]), isFalse); + }); + }); +} diff --git a/packages/dart/test/utils/sample_rate_format_test.dart b/packages/dart/test/utils/sample_rate_format_test.dart new file mode 100644 index 0000000000..a9b4c80b09 --- /dev/null +++ b/packages/dart/test/utils/sample_rate_format_test.dart @@ -0,0 +1,49 @@ +import 'package:sentry/src/utils/sample_rate_format.dart'; +import 'package:test/test.dart'; +import 'package:intl/intl.dart'; + +void main() { + test('format', () { + final inputs = [ + 0.0, + 1.0, + 0.1, + 0.11, + 0.19, + 0.191, + 0.1919, + 0.19191, + 0.191919, + 0.1919191, + 0.19191919, + 0.191919191, + 0.1919191919, + 0.19191919191, + 0.191919191919, + 0.1919191919191, + 0.19191919191919, + 0.191919191919191, + 0.1919191919191919, + 0.19191919191919199, + ]; + + for (final input in inputs) { + expect( + SampleRateFormat().format(input), + NumberFormat('#.################').format(input), + ); + } + }); + + test('input smaller 0 is capped', () { + expect(SampleRateFormat().format(-1), '0'); + }); + + test('input larger 1 is capped', () { + expect(SampleRateFormat().format(1.1), '1'); + }); + + test('call with NaN returns 0', () { + expect(SampleRateFormat().format(double.nan), '0'); + }); +} diff --git a/packages/dart/test/utils/tracing_utils_test.dart b/packages/dart/test/utils/tracing_utils_test.dart new file mode 100644 index 0000000000..b5e7b70b52 --- /dev/null +++ b/packages/dart/test/utils/tracing_utils_test.dart @@ -0,0 +1,357 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:test/test.dart'; + +import '../mocks/mock_sentry_client.dart'; +import '../test_utils.dart'; + +void main() { + group('$containsTargetOrMatchesRegExp', () { + final origins = ['localhost', '^(http|https)://api\\..*\$']; + + test('origins contains the url when it contains one of the defined origins', + () { + expect( + containsTargetOrMatchesRegExp(origins, 'http://localhost:8080/foo'), + isTrue); + expect( + containsTargetOrMatchesRegExp( + origins, 'http://xxx.localhost:8080/foo'), + isTrue); + }); + + test('origins contain the url when it matches regex', () { + expect( + containsTargetOrMatchesRegExp(origins, 'http://api.foo.bar:8080/foo'), + isTrue); + expect( + containsTargetOrMatchesRegExp( + origins, 'https://api.foo.bar:8080/foo'), + isTrue); + expect( + containsTargetOrMatchesRegExp( + origins, 'http://api.localhost:8080/foo'), + isTrue); + expect( + containsTargetOrMatchesRegExp(origins, 'ftp://api.foo.bar:8080/foo'), + isFalse); + }); + + test('invalid regex do not throw', () { + expect( + containsTargetOrMatchesRegExp( + ['AABB???', '^(http|https)://api\\..*\$'], + 'http://api.foo.bar:8080/foo'), + isTrue); + }); + + test('when no origins are defined, returns false for every url', () { + expect(containsTargetOrMatchesRegExp([], 'api.foo.com'), isFalse); + }); + }); + + group('$addSentryTraceHeaderFromSpan', () { + final fixture = Fixture(); + + test('adds sentry trace header from span', () { + final headers = {}; + final sut = fixture.getSut(); + final sentryHeader = sut.toSentryTrace(); + + addSentryTraceHeaderFromSpan(sut, headers); + + expect(headers[sentryHeader.name], sentryHeader.value); + }); + + test('adds sentry trace header', () { + final headers = {}; + final sut = fixture.getSut(); + final sentryHeader = sut.toSentryTrace(); + + addSentryTraceHeader(sentryHeader, headers); + + expect(headers[sentryHeader.name], sentryHeader.value); + }); + }); + + group('$addBaggageHeader', () { + final fixture = Fixture(); + + test('adds baggage header', () { + final headers = {}; + final sut = fixture.getSut(); + final baggage = sut.toBaggageHeader(); + + addBaggageHeader(sut.toBaggageHeader()!, headers); + + expect(headers[baggage!.name], baggage.value); + }); + + test('adds baggage header from span', () { + final headers = {}; + final sut = fixture.getSut(); + final baggage = sut.toBaggageHeader(); + + addBaggageHeaderFromSpan(sut, headers); + + expect(headers[baggage!.name], baggage.value); + }); + + test('appends baggage header from span', () { + final headers = {}; + final oldValue = 'other-vendor-value-1=foo'; + headers['baggage'] = oldValue; + + final sut = fixture.getSut(); + final baggage = sut.toBaggageHeader(); + + final newValue = '$oldValue,${baggage!.value}'; + + addBaggageHeaderFromSpan(sut, headers); + + expect(headers[baggage.name], newValue); + }); + + test('overwrites duplicate key values', () { + final headers = {}; + final oldValue = + 'other-vendor-value=foo,sentry-trace_id=${SentryId.newId()},sentry-public_key=oldPublicKey,sentry-release=oldRelease,sentry-environment=oldEnvironment,sentry-user_id=oldUserId,sentry-transaction=oldTransaction,sentry-sample_rate=0.5'; + + headers['baggage'] = oldValue; + + final sut = fixture.getSut(); + final baggage = sut.toBaggageHeader(); + + addBaggageHeaderFromSpan(sut, headers); + + expect(headers[baggage!.name], + 'other-vendor-value=foo,sentry-trace_id=${sut.context.traceId},sentry-public_key=public,sentry-release=release,sentry-environment=environment,sentry-transaction=name,sentry-sample_rate=1,sentry-sampled=true'); + }); + }); + + group('$isValidSampleRate', () { + test('returns false if null sampleRate', () { + expect(isValidSampleRate(null), false); + }); + + test('returns true if 1', () { + expect(isValidSampleRate(1.0), true); + }); + + test('returns true if 0', () { + expect(isValidSampleRate(0.0), true); + }); + + test('returns false if below the range', () { + expect(isValidSampleRate(-0.01), false); + }); + + test('returns false if above the range', () { + expect(isValidSampleRate(1.01), false); + }); + + test('returns false if NaN', () { + expect(isValidSampleRate(double.nan), false); + }); + }); + + group('$generateSentryTraceHeader', () { + test('generates header with new ids when not provided', () { + final header = generateSentryTraceHeader(); + + expect(header.traceId, isNotNull); + expect(header.spanId, isNotNull); + expect(header.sampled, isNull); + }); + + test('generates header with provided traceId', () { + final traceId = SentryId.newId(); + final header = generateSentryTraceHeader(traceId: traceId); + + expect(header.traceId, traceId); + expect(header.spanId, isNotNull); + expect(header.sampled, isNull); + }); + + test('generates header with provided spanId', () { + final spanId = SpanId.newId(); + final header = generateSentryTraceHeader(spanId: spanId); + + expect(header.traceId, isNotNull); + expect(header.spanId, spanId); + expect(header.sampled, isNull); + }); + + test('generates header with provided sampled decision', () { + final header = generateSentryTraceHeader(sampled: true); + + expect(header.traceId, isNotNull); + expect(header.spanId, isNotNull); + expect(header.sampled, true); + }); + + test('generates header with all parameters provided', () { + final traceId = SentryId.newId(); + final spanId = SpanId.newId(); + final header = generateSentryTraceHeader( + traceId: traceId, + spanId: spanId, + sampled: false, + ); + + expect(header.traceId, traceId); + expect(header.spanId, spanId); + expect(header.sampled, false); + }); + }); + + group('$addTracingHeadersToHttpHeader', () { + final fixture = Fixture(); + + test('adds headers from span when span is provided', () { + final headers = {}; + final hub = fixture._hub; + final span = fixture.getSut(); + + addTracingHeadersToHttpHeader(headers, hub, span: span); + + final traceHeader = + SentryTraceHeader.fromTraceHeader(headers['sentry-trace']); + expect(traceHeader.traceId, span.context.traceId); + expect(traceHeader.spanId, span.context.spanId); + expect(traceHeader.sampled, span.samplingDecision?.sampled); + expect(headers['baggage'], isNotNull); + }); + + test('adds headers from scope when span is null', () { + final headers = {}; + final hub = fixture._hub; + hub.configureScope((scope) { + scope.propagationContext.baggage = SentryBaggage({'test': 'value'}); + }); + + addTracingHeadersToHttpHeader(headers, hub); + + final traceHeader = + SentryTraceHeader.fromTraceHeader(headers['sentry-trace']); + expect(traceHeader.traceId, hub.scope.propagationContext.traceId); + expect(headers['baggage'], contains('test=value')); + }); + }); + + group('$addSentryTraceHeaderFromScope', () { + test('adds sentry trace header from scope propagation context', () { + final fixture = Fixture(); + final headers = {}; + final hub = fixture._hub; + final scope = hub.scope; + + addSentryTraceHeaderFromScope(scope, headers); + + final traceHeader = + SentryTraceHeader.fromTraceHeader(headers['sentry-trace']); + expect(traceHeader.traceId, scope.propagationContext.traceId); + }); + }); + + group('$addBaggageHeaderFromScope', () { + test('adds baggage header from scope when baggage exists', () { + final fixture = Fixture(); + final headers = {}; + final hub = fixture._hub; + final scope = hub.scope; + scope.propagationContext.baggage = SentryBaggage({ + 'sentry-trace_id': scope.propagationContext.traceId.toString(), + 'sentry-public_key': 'public', + 'custom': 'value', + }); + + addBaggageHeaderFromScope(scope, headers); + + expect(headers['baggage'], isNotNull); + expect(headers['baggage'], contains('custom=value')); + expect(headers['baggage'], contains('sentry-public_key=public')); + }); + + test('does not add baggage header when baggage is null', () { + final fixture = Fixture(); + final headers = {}; + final hub = fixture._hub; + final scope = hub.scope; + scope.propagationContext.baggage = null; + + addBaggageHeaderFromScope(scope, headers); + + expect(headers['baggage'], isNull); + }); + }); + + group('$isValidSampleRand', () { + test('returns false if null sampleRand', () { + expect(isValidSampleRand(null), false); + }); + + test('returns true if 0', () { + expect(isValidSampleRand(0.0), true); + }); + + test('returns true if 0.5', () { + expect(isValidSampleRand(0.5), true); + }); + + test('returns true if 0.999', () { + expect(isValidSampleRand(0.999), true); + }); + + test('returns false if 1.0', () { + expect(isValidSampleRand(1.0), false); + }); + + test('returns false if below the range', () { + expect(isValidSampleRand(-0.01), false); + }); + + test('returns false if above the range', () { + expect(isValidSampleRand(1.01), false); + }); + + test('returns false if NaN', () { + expect(isValidSampleRand(double.nan), false); + }); + }); +} + +class Fixture { + Fixture() { + _hub = Hub(_options); + _hub.configureScope((scope) => scope.setUser(_user)); + + _hub.bindClient(_client); + } + + final _context = SentryTransactionContext( + 'name', + 'op', + transactionNameSource: SentryTransactionNameSource.custom, + samplingDecision: SentryTracesSamplingDecision( + true, + sampleRate: 1.0, + ), + ); + + final _options = defaultTestOptions() + ..release = 'release' + ..environment = 'environment'; + + late Hub _hub; + + final _client = MockSentryClient(); + + final _user = SentryUser( + id: 'id', + ); + + SentryTracer getSut() { + return SentryTracer(_context, _hub); + } +} diff --git a/packages/dart/test/utils/url_details_test.dart b/packages/dart/test/utils/url_details_test.dart new file mode 100644 index 0000000000..673d4452da --- /dev/null +++ b/packages/dart/test/utils/url_details_test.dart @@ -0,0 +1,93 @@ +import 'package:mockito/mockito.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + test('does not crash on null span', () { + final urlDetails = + UrlDetails(url: "https://sentry.io/api", query: "q=1", fragment: "top"); + urlDetails.applyToSpan(null); + }); + + test('applies all to span', () { + final urlDetails = + UrlDetails(url: "https://sentry.io/api", query: "q=1", fragment: "top"); + final span = MockSpan(); + urlDetails.applyToSpan(span); + + verify(span.setData("url", "https://sentry.io/api")); + verify(span.setData("http.query", "q=1")); + verify(span.setData("http.fragment", "top")); + }); + + test('applies only url to span', () { + final urlDetails = UrlDetails(url: "https://sentry.io/api"); + final span = MockSpan(); + urlDetails.applyToSpan(span); + + verify(span.setData("url", "https://sentry.io/api")); + verifyNoMoreInteractions(span); + }); + + test('applies only query to span', () { + final urlDetails = UrlDetails(query: "q=1"); + final span = MockSpan(); + urlDetails.applyToSpan(span); + + verify(span.setData("http.query", "q=1")); + verifyNoMoreInteractions(span); + }); + + test('applies only fragment to span', () { + final urlDetails = UrlDetails(fragment: "top"); + final span = MockSpan(); + urlDetails.applyToSpan(span); + + verify(span.setData("http.fragment", "top")); + verifyNoMoreInteractions(span); + }); + + test('applies details to request', () { + final url = "https://sentry.io/api?q=1#top"; + final request = SentryRequest(url: url); + request.sanitize(); + + expect(request.url, "https://sentry.io/api"); + expect(request.queryString, "q=1"); + expect(request.fragment, "top"); + }); + + test('applies details without fragment and url to request', () { + final request = SentryRequest(url: 'https://sentry.io/api'); + request.sanitize(); + + expect(request.url, "https://sentry.io/api"); + expect(request.queryString, isNull); + expect(request.fragment, isNull); + }); + + test('removes cookies from request', () { + final request = + SentryRequest(url: 'https://sentry.io/api', cookies: 'foo=bar'); + request.sanitize(); + expect(request.cookies, isNull); + }); + + test('returns fallback for null URL', () { + final urlDetails = UrlDetails(url: null); + expect(urlDetails.urlOrFallback, "unknown"); + }); + + test('returns fallback for invalid Uri', () { + final urlDetails = UrlDetails(url: 'htttps://[Filtered].com/foobar.txt'); + + expect(urlDetails.urlOrFallback, "unknown"); + expect(Uri.parse(urlDetails.urlOrFallback), isNotNull); + }); +} + +class MockSpan extends Mock implements SentrySpan { + final SentrySpanContext _context = SentrySpanContext(operation: 'test'); + @override + SentrySpanContext get context => _context; +} diff --git a/packages/dart/test/utils_test.dart b/packages/dart/test/utils_test.dart new file mode 100644 index 0000000000..a2012a6fe3 --- /dev/null +++ b/packages/dart/test/utils_test.dart @@ -0,0 +1,34 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:sentry/src/protocol/sentry_device.dart'; +import 'package:test/test.dart'; + +import 'package:sentry/src/utils.dart'; + +void main() { + group('formatDateAsIso8601WithSecondPrecision', () { + test('strips sub-millisecond parts', () { + final testDate = + DateTime.fromMillisecondsSinceEpoch(1502467721598, isUtc: true); + expect(testDate.toIso8601String(), '2017-08-11T16:08:41.598Z'); + expect(formatDateAsIso8601WithMillisPrecision(testDate), + '2017-08-11T16:08:41.598Z'); + }); + + test('non enum returns toString serialization', () { + final value = true; + expect(jsonSerializationFallback(value), 'true'); + }); + + test('enum returns described enum during serialization', () { + expect(jsonSerializationFallback(SentryOrientation.landscape), + 'SentryOrientation.landscape'); + }); + + test('null Object returns null during serialization', () { + expect(jsonSerializationFallback(null), null); + }); + }); +} diff --git a/packages/dart/test/version_test.dart b/packages/dart/test/version_test.dart new file mode 100644 index 0000000000..ada7310338 --- /dev/null +++ b/packages/dart/test/version_test.dart @@ -0,0 +1,22 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('vm') +library; + +import 'dart:io'; + +import 'package:sentry/src/version.dart'; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart' as yaml; + +void main() { + group('sdkVersion', () { + test('matches that of pubspec.yaml', () { + final dynamic pubspec = + yaml.loadYaml(File('pubspec.yaml').readAsStringSync()); + expect(sdkVersion, pubspec['version']); + }); + }); +} diff --git a/packages/dart/test_resources/envelope-no-attachment.envelope b/packages/dart/test_resources/envelope-no-attachment.envelope new file mode 100644 index 0000000000..0faac03efa --- /dev/null +++ b/packages/dart/test_resources/envelope-no-attachment.envelope @@ -0,0 +1,3 @@ +{"event_id":"00000000000000000000000000000000","sdk":{"name":"","version":""},"dsn":"https://abc@def.ingest.sentry.io/1234567"} +{"content_type":"application/json","type":"event","length":86} +{"event_id":"00000000000000000000000000000000","timestamp":"1970-01-01T00:00:00.000Z"} \ No newline at end of file diff --git a/packages/dart/test_resources/envelope-with-image.envelope b/packages/dart/test_resources/envelope-with-image.envelope new file mode 100644 index 0000000000000000000000000000000000000000..533f5e4f6fc9c60af5fbb0311fdb5fdfc2e074e9 GIT binary patch literal 3712 zcmV-`4uA1`B4u`EZggL1WFk5uGh#D1GG;O|Wo0%uG&wP5I51{6WMySBI506bVK-ti zA}k_vWNRWidm?UOZDk@lB6MYQbRsMwc4cyNX>V>KIwE#ua&u{KZX$hs3VR}BZ*FvD zZggLCd2nSSIwEOpVP|D8aBgQJEFxxUY-Mg?ZDk@lB6DSKbaHtvaBgQJEFyGyaAhJo zB4KoNVPj}*Wo~pLEFx@WZfA68B04iQGc|n*iBL{Q4GJ0x0000DNk~Le0002t0002t z2m=5B010g(NB{r;hfqvZMgRZ*0002#>FMU?=5B6oL_|cdudn?4{Q3F$?Ck9I_4Ufi z%J1**_xJaHetwaWkx5BOzrVlV-`~y6&DPe|TwGi@I5<8&K4D>DxVX4xW@gjV(C;AvQ<@8V#tQV z000dGNklX+z>l6NQb-un3BCU1ku`I9_y?|Nr~m0TF?Es=7K!C*i(1A0=R~ zb*fHPcaDxabm-8bLx&C>I&}EILw__F44ZC5G7PUS^7+fd!|d+n^1R=4YpTcjO}2yS zKTWr&T3p>`0=MT)x2ifE-^l(gaXMAeb@0E? zMP&{5x|zlCkE?@+ zY){XeJ-lnWp+)iC*}*LP(sWY`!rj3v>owihLb$}f%wOrR7fm;|2#_g$433U|(Fcd< z+5Dsqu0Ht}KKa&gYYX6-W@`NzeROz?F@auuUw?K5pUoR>Z2?dxCYw)l`1DtUtu2BD z^x=NKsu+UNpdo+pn#uu6fO^L1cet~aH%x^S` z6KHNovA=`wd=Gr5IsAdvhRS9RiZQ6M;mp|-zSY*GDO3ezC&6#g(sq2U36RmSnBoIu zo=%guwg85tOSryCli0?pH<}8cQKXuw7A8$8)#N2M$`$`k+S(!@C)(?rV7|X?5;!r1 z-@&As4Udtk6SUC$wUq}Tfs?U_3G^r<-l{U2J}^a`y!!%OY*W&drXWsmp~{q5qSh7w z35c;JhdKO4dy=N)JZF=*g1me9iA7r^aIJhTZVIS&qb*5ODoR-4CPq{m(l4}wF^uxH zMU1(j%y=u3rag)&Hi+;{dZa|QfibvzZG$_1FiUyI79>qk58Q!&rIOCmf~4sHxndh` z)dlnaZ8$k}PXf(0CSG2n8jh!gE%)Quv^IZtlQZ#&jM0vy>Bv0Xl(|J-%8=)2o9@On ziPu~3ZN}alzS^3k>2HdFKH(Dxi7{RhH{6dg$!(wbST3xs|m+IW(rnWw5 zY9HnD_&2RDvpG>CjU^oV;sKP|nA7O+q$yK=mcH3B_=<-{)^M19a*^7WG^M@dZ!4+G zFKv^+iPk<4&PWhrw64tN*f^ONoJBlP)V`CZhNl|bi!Df+4jmax$_YEWI3@?M-ZGn~ z>}S}|xYixj&;Zfs)S5J1xmIC)VW2y4A(@cAS!dETU;W3|Fn#M+Mg*1ssmw3qikctB zMj<^7E>$($cnw4vG&Z%B+4N_h|MW3*f>-t70`<?ovfp{1%y;(>sHbl93s zn(mM8G@$ok<=PUqo6@Ia8}*dg$Q=?mPOo0AVACRH&TF{{g{I`jo<=+#vxLnV!i=kK^xi5$~5R#b#XQ>$hSr~-LUcLhW5aU3tig*0Q6}YgT2_l$O;e=?+zU zquTW7dSEKdtgy_6hJ5E-nwp+a^-ZD-OAJlS7$mRlHb+fQXqj6&8*3I^Bu$!UsTCp&d*;I5lfpdLm#Hbl{65q@dB*_@VT}r1# zR~nVm$XSd*LsCi7l0Fqp6jq!xWxKJ&)KK$KbI`04ID2OBj)tLdVJ4nJTI1H>A&hU8(vlvqk?BuY${6+= zMhuI9%&&L}U>?l|_lPlyO5lOl;22(S#6@#-(#cHK)9@88%pBRXXsEcPYbfo8Nwg2C zQ`l8JX?m}!OiSo)FO_U)5jiY7)BfW&FTohPJv9fL9CpQmz$DsPiK;JjAE17_hE7Ew zhHEq+*~KnKz{1Q@61ZAim>CO|3Plu?2Nun)L9G66f}SyrY9s{?*8aWhYXI*CZr`br zG1f5hziNFsP=;difW_^=7GD8UGHH6Om))z>FWnWzcLP@}kO9r19i_``)Rnr>+(OvD zO2efB6DXCfnlav?@`k!TewpD=faLAS@uBE^nC?RL)i|L?zNP&c#vONBG+7)z-cut zY{vLVhfV_AiIIlF(K0q-pHnW=U=QPyrdwljW*Tdho|P>iy;HW5a_xr`XVJIW?C|r2 zHQjVLP&21OljLiW#*axm$R0#4>dVy;KaKyR`D&c*8D5 z)O^ZXbdKcYhIN#6MI}u^E-}Oe`f0^m7C$`0a9sMPo@2iex7nZ!X^dhHkMky%_5hdZ zSj@GjmISl7{3zYi2!%FU?8V@=D3>x<;QS|0~Cv}b1Lh# zkT2v$^z{%Aitnx_lI%TX1e=_VgD~m*>*Cj(rH4Vrz$=P9#~Hv_+RSxv3DVu;=>K;ww6XAmwYkGpsbbz8BkqY6hMBnOuy`n`kFDMGJCtRUR3 z!sJF-1!+|lboy?7brOStl-zvo3KF7-5wM3tY{_>}_}`9l`N#4=LiI%#f>e-t%B z9#o8#7f_Z2?l9+dZ~MR_VeTF-oe%4*K~eaeY9DaLyL-6uD$Ezd6a$L!1$&6$ledTO zK}3Q{#OS1n9XhjeAzG%PpE*5R*q2N(0#Vd;DdpLve=GZ>g9EHYv_^cd~bKQjO zLGm7+Pfr1NOTl7sc4^h6-ak(#^B2dZ);5y!VL9|igX5Awov>Hzx6;4vp+kob9XfRA e(4j+z^!OjB9jyEhAyhE{0000E`C4~jn{Grh46iQo`OCw@?C$3Byx(+ds>k_Fwu9+EO}D68T-{~@x93f_syZCs$o?&H zI%>LQ72!Ac?=K#%n{Hbrcv3s~7yp5k+WTg8@W0SSWea}dYtt<(iPKOAv)iVdSPsv@ z4rbY9(~T^HV@C(G?6~P>mOyxoE-`JonZ@ystAmGZPtTk^ylc9lMe*I)!7TgIbW;n$ z-N7vDHQm-ixWvB9U+J$GO*gg(kSTr)j*foO2Z!j{{G<-9KKU0u`POi23*ee&YW*2~ zba;$0fnI!He|80*%^PiP0Z=C*m__@4;&I>&=fuHJVAluO+&3M0NQ4Yy=NZanX5)xTLihI_2=-+TO+M40Ah+i z;wh4-o*QUw5xha$rSyx8aTs|iG9pj#+yy-M6P{}wkSSP}d7n%l5iLLg2fp1LAZ#0S%Xl_Wczk~054}7ON{DIbn%4QCV zF{rWO%-Iya)z+jbR0U-x!Ee#hc6_Y~kkPQ1;sa!!PLsE`0EVPXxV}k~*v6_inhKv$ zq?)M~CQT{Th_NMyIs8U@lBVQ5XOp;synFbG zMO!3rt$ZzR3aECYElE=;VjFJN1@r%HI5~7r0?jriUS6Xbj;Dkz z_v6{LHh*`MGx3Rx(T=3)$UNMXxkX;ekmqTe?#4BV*IV#y#@-yh+M1;4Z;F6E;S&gn zFbHIZJ+q#vNu#_^BDeGUAQ-w>fGa|wmxZUALa7+H?1$TIZ-2xB^>(V0hHO8 z)9CP|DN}xyzS%MOiibwlaF~8_k=m9trM={DE2+ybZIi%>);JG12@jhtr=0M-z0wePSU#xjF4{)i{ca`WwIh^lL^ag#(^Z-5>`y5 zxZzgFKs=MM%*K+?Hn3`7IG8d^^lD9-YP`LNZ#s?lc^_Nu0y0LbGMn7j!)&6P$74De zWjiW0CQbbpTg~+rGu=EH{}#(^k|a&Hhuy8N2r>MXkZNbjN^Y@m*6 zMnyS%Awii9sR~nFE}W@d?4bZ>aU5E>>dR~(m#`R)^Wo9Md*dRD?%Ut7Z(S0(nn(>5iP9)udusGb*ZqG3Z~c1qJA;zdQ zX-ZpXQx|zc?Oj1#dB}>^vZK&zR%OzZmenWe4pn=j+VtppU@FY4u*`;reCJ%6nx0Vg zO`;4-3{A`!B(Lo@M@>&?nOiy=YZhE2O`4Lq+}GDNkLOm~EQaku3=<|z@wbIp&{Rg1 znQ5I`u`rd{RCG6ibA4#Us2Oz<-^>#v$r!_3N~c9v8kN(?S&TtLQc2R3>@*Xd(nT%* z-iK#R+E0`;MJG}*(@Gb$R?xb(YzN*=P`)sevUn=hDtl3DHmEkHqMamZN-XfEyGwWr zvU*gKc9x}2V3|!x2^<%OMO);K>FX4{K~vGDHBz!L6B99}$IHcDian(PH0Okz{8C9% zj*RU0-{4D0v|tW)mRb6QCr!&Rh9<9BI#3T)Uxk|KT$rg8R-80tyRpR7Q1ei8(5w?U zduH&ChM{m_CZ0lCH%@)-dwFYJE9ShGO!7#qGcrUjb4w zX?m=e-K*3u-4(@m16M4N0nMQurORy8mAcT}LfF4b!=(ZfD3z_6G2Wr_hPpoFpeR)r zuxA6dF-_7`wW%7HL&{2-MCn)rLL#gJEq~JV8mnP4-9?pVT`{MU^&bLlA*Z>5!pwmd z5^Gyg4hd@xpq+-w@)NxRWUO>i8$HP~%7rs>UxoZIOqy0uW;0UG_-b;HOw92(OXD~X zrFc`_O)w&fI8)!fA-P%cP9@)l>;@)Oswm7Hu({Ffsf-l4<6$~8FD#^ zPxzelYl7!vP)7Q?`OR1BcIwDR6~!!AYCe9BsMj^yNqb(D2QB~3vtF~kJ= zX~kO>KRm;5T>7S-W4{r%*`N$*jA9Ot^Cp+}0GH`l%(bVM1hcsODBbXA2|{E~6_OSq zJWsZ0flCB+Crv4VY{y%ml_+AhaI3jGLl|hi9DWP#SAbBu$mV@AHoS9WBhV(v)$BSF z>%7pNG~ZAm2WTZq^dfc`FMnaoeRUSlbh*^)=zo1Be< zFzNj3;@6y|he5``D~dhG8NgCI<6Dj<^Zm!13TW`8DOJh(%$N+&*!>n`_=4V&SQFk; zc!4q-O2;|xNr5OEn=a31qM;U?<<7Q{1pc|#N$zmobpt0$d0`3t79BjX>fIO85T9@4 z5)zi&g;ZWSUU&yMJJP0m+i{dbFV>@2?zIcjP;+`oEay99MBzKSPyQHh!6qRfqM%Va zr^pv_cChWj?E8qm;zwngc&Q2aj8`4E}%mzyc!h!1YN{b z#}co9C8~%#LoD41^s0rPf^ojMhc4=P8u(_v$Y+f#X^{@T8D7Ig<5Fvcm= zdBm2bNMnqFrQEL=H{^Q{xulv6q+y?fKNp+r?~6g=l_i8tbPV@dO|7_Mh~6wfhm4^3B7Ww7|`nz>+fN5CLyK)cWe0K zxDQ;KH{cfruD^o+2^~{^53kI_O@;A9t48XS@RE(7uP?iMlFz0n|`@kb%?jA0k z59_Q!QTUu{A8^IHd${r{%ooEH1B&qldx+taw}2-mm2)C0h4uGv%eAW7v1vyScdT&e^@WjWMGyDBXutL@ z7H#OE;885aD~jfOID_!!*gn=Ag~ogMtQ?Ya-GuBx@*bW~PXTvJ!D4ZCY1O6PKTjv~ z7ssX6Hj?vUIrK+^I&|pJp+krC_#dbpto#olR51Vm002ov JPDHLkV1hwO&o}@8 literal 0 HcmV?d00001 diff --git a/packages/dart/test_resources/testfile.txt b/packages/dart/test_resources/testfile.txt new file mode 100644 index 0000000000..96c906756d --- /dev/null +++ b/packages/dart/test_resources/testfile.txt @@ -0,0 +1 @@ +foo bar \ No newline at end of file diff --git a/packages/dart/tool/presubmit.sh b/packages/dart/tool/presubmit.sh new file mode 100755 index 0000000000..b64e684920 --- /dev/null +++ b/packages/dart/tool/presubmit.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +set -e +set -x + +# https://dart.dev/tools/dart-tool + +# get current package's dependencies +dart pub get +# static code analyzer +dart analyze --fatal-infos +# tests +dart test -p "chrome,vm" +# formatting +dart format --set-exit-if-changed ./ +# pub score +pana +# dry publish +dart pub publish --dry-run From b5e5b420693ea7b7eda88ffae491b6a2c07347b9 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 30 Jul 2025 15:00:33 +0200 Subject: [PATCH 05/45] Update --- flutter/example/integration_test/profiling_test.dart | 2 +- flutter/example/pubspec_overrides.yaml | 2 +- metrics/prepare-dart.sh | 2 +- metrics/prepare.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flutter/example/integration_test/profiling_test.dart b/flutter/example/integration_test/profiling_test.dart index ef9efef711..0ebf4d1c0c 100644 --- a/flutter/example/integration_test/profiling_test.dart +++ b/flutter/example/integration_test/profiling_test.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import '../../../dart/test/mocks/mock_transport.dart'; +import '../../../packages/dart/test/mocks/mock_transport.dart'; void main() { final transport = MockTransport(); diff --git a/flutter/example/pubspec_overrides.yaml b/flutter/example/pubspec_overrides.yaml index 8f3cdc6729..47527b0a71 100644 --- a/flutter/example/pubspec_overrides.yaml +++ b/flutter/example/pubspec_overrides.yaml @@ -1,6 +1,6 @@ dependency_overrides: sentry: - path: ../../dart + path: ../../packages/dart sentry_flutter: path: ../ sentry_dio: diff --git a/metrics/prepare-dart.sh b/metrics/prepare-dart.sh index f0bac4730e..5e71f00a8d 100755 --- a/metrics/prepare-dart.sh +++ b/metrics/prepare-dart.sh @@ -26,7 +26,7 @@ cat <>"$pubspec" dependency_overrides: sentry: - path: ../../dart + path: ../../packages/dart EOF diff --git a/metrics/prepare.sh b/metrics/prepare.sh index 98d9ca0ee3..66e62cde6f 100755 --- a/metrics/prepare.sh +++ b/metrics/prepare.sh @@ -26,7 +26,7 @@ cat <>"$pubspec" dependency_overrides: sentry: - path: ../../dart + path: ../../packages/dart sentry_flutter: path: ../../flutter From 75e40d49e3c84ebcb90d153f00fa4d2517d63349 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 30 Jul 2025 16:11:32 +0200 Subject: [PATCH 06/45] Update --- dio/dartdoc_options.yaml | 2 +- drift/dartdoc_options.yaml | 2 +- file/dartdoc_options.yaml | 2 +- firebase_remote_config/dartdoc_options.yaml | 2 +- flutter/dartdoc_options.yaml | 2 +- hive/dartdoc_options.yaml | 2 +- isar/dartdoc_options.yaml | 2 +- logging/dartdoc_options.yaml | 2 +- sqflite/dartdoc_options.yaml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dio/dartdoc_options.yaml b/dio/dartdoc_options.yaml index 7cbb8c0d74..fcb0f9cc26 120000 --- a/dio/dartdoc_options.yaml +++ b/dio/dartdoc_options.yaml @@ -1 +1 @@ -../dart/dartdoc_options.yaml \ No newline at end of file +../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/drift/dartdoc_options.yaml b/drift/dartdoc_options.yaml index 7cbb8c0d74..fcb0f9cc26 120000 --- a/drift/dartdoc_options.yaml +++ b/drift/dartdoc_options.yaml @@ -1 +1 @@ -../dart/dartdoc_options.yaml \ No newline at end of file +../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/file/dartdoc_options.yaml b/file/dartdoc_options.yaml index 7cbb8c0d74..fcb0f9cc26 120000 --- a/file/dartdoc_options.yaml +++ b/file/dartdoc_options.yaml @@ -1 +1 @@ -../dart/dartdoc_options.yaml \ No newline at end of file +../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/firebase_remote_config/dartdoc_options.yaml b/firebase_remote_config/dartdoc_options.yaml index 7cbb8c0d74..fcb0f9cc26 120000 --- a/firebase_remote_config/dartdoc_options.yaml +++ b/firebase_remote_config/dartdoc_options.yaml @@ -1 +1 @@ -../dart/dartdoc_options.yaml \ No newline at end of file +../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/flutter/dartdoc_options.yaml b/flutter/dartdoc_options.yaml index 7cbb8c0d74..fcb0f9cc26 120000 --- a/flutter/dartdoc_options.yaml +++ b/flutter/dartdoc_options.yaml @@ -1 +1 @@ -../dart/dartdoc_options.yaml \ No newline at end of file +../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/hive/dartdoc_options.yaml b/hive/dartdoc_options.yaml index 7cbb8c0d74..fcb0f9cc26 120000 --- a/hive/dartdoc_options.yaml +++ b/hive/dartdoc_options.yaml @@ -1 +1 @@ -../dart/dartdoc_options.yaml \ No newline at end of file +../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/isar/dartdoc_options.yaml b/isar/dartdoc_options.yaml index 7cbb8c0d74..fcb0f9cc26 120000 --- a/isar/dartdoc_options.yaml +++ b/isar/dartdoc_options.yaml @@ -1 +1 @@ -../dart/dartdoc_options.yaml \ No newline at end of file +../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/logging/dartdoc_options.yaml b/logging/dartdoc_options.yaml index 7cbb8c0d74..fcb0f9cc26 120000 --- a/logging/dartdoc_options.yaml +++ b/logging/dartdoc_options.yaml @@ -1 +1 @@ -../dart/dartdoc_options.yaml \ No newline at end of file +../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/sqflite/dartdoc_options.yaml b/sqflite/dartdoc_options.yaml index 7cbb8c0d74..fcb0f9cc26 120000 --- a/sqflite/dartdoc_options.yaml +++ b/sqflite/dartdoc_options.yaml @@ -1 +1 @@ -../dart/dartdoc_options.yaml \ No newline at end of file +../packages/dart/dartdoc_options.yaml \ No newline at end of file From 086519bdaec2cabd17b0a76cf4b805fe1f8a0a3a Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 30 Jul 2025 16:15:33 +0200 Subject: [PATCH 07/45] Update --- .github/workflows/analyze.yml | 2 +- e2e_test/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml index 139bd091bc..0a4bc81055 100644 --- a/.github/workflows/analyze.yml +++ b/.github/workflows/analyze.yml @@ -73,7 +73,7 @@ jobs: if: ${{ inputs.package == 'flutter' }} working-directory: ${{ inputs.package }} run: | - sed -i.bak 's|sentry:.*|sentry:\n path: /github/workspace/dart|g' pubspec.yaml + sed -i.bak 's|sentry:.*|sentry:\n path: /github/workspace/packages/dart|g' pubspec.yaml - uses: axel-op/dart-package-analyzer@56afb7e6737bd2b7cee05382ae7f0e8111138080 # pin@v3 id: analysis with: diff --git a/e2e_test/pubspec.yaml b/e2e_test/pubspec.yaml index d0ec1b711c..f79e955bfb 100644 --- a/e2e_test/pubspec.yaml +++ b/e2e_test/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: sentry: path: - ./../dart + ./../packages/dart http: ^0.13.0 dev_dependencies: From f977b385cd6cc2b55a5328de70f46918860868de Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 11:26:16 +0200 Subject: [PATCH 08/45] Fix CHANGELOG --- packages/dart/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) delete mode 120000 packages/dart/CHANGELOG.md diff --git a/packages/dart/CHANGELOG.md b/packages/dart/CHANGELOG.md deleted file mode 120000 index 04c99a55ca..0000000000 --- a/packages/dart/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../CHANGELOG.md \ No newline at end of file From ecce8da6be90b9b5cd10dc0293be5ba0c8a9b29b Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 11:27:08 +0200 Subject: [PATCH 09/45] Update gitignore --- .gitignore | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index df8f6f05dd..2ee4c21264 100644 --- a/.gitignore +++ b/.gitignore @@ -16,15 +16,7 @@ build/ .fvmrc .test_coverage.dart -dart/coverage/* -logging/coverage/* -dio/coverage/* -file/coverage/* -flutter/coverage/* -sqflite/coverage/* -drift/coverage/* -hive/coverage/* -isar/coverage/* +packages/**/coverage/* pubspec.lock Podfile.lock From c692b297aaa5e27f93a0bbc2ab636fe309f61572 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 11:27:37 +0200 Subject: [PATCH 10/45] Fix symlink in CHANGELOG --- packages/dart/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) create mode 120000 packages/dart/CHANGELOG.md diff --git a/packages/dart/CHANGELOG.md b/packages/dart/CHANGELOG.md new file mode 120000 index 0000000000..699cc9e7b7 --- /dev/null +++ b/packages/dart/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file From 0cae93e6bde8918295292d0f2fc9fc2238c7e567 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 11:57:10 +0200 Subject: [PATCH 11/45] Add logging to melos --- .github/workflows/logging.yml | 10 +++++----- logging/CHANGELOG.md | 1 - logging/lib/sentry_logging.dart | 3 --- {logging => packages/logging}/.gitignore | 0 packages/logging/CHANGELOG.md | 1 + {logging => packages/logging}/LICENSE | 0 {logging => packages/logging}/README.md | 0 {logging => packages/logging}/analysis_options.yaml | 0 {logging => packages/logging}/class-diagram.svg | 0 {logging => packages/logging}/dartdoc_options.yaml | 0 .../logging}/example/sentry_logging_example.dart | 0 packages/logging/lib/sentry_logging.dart | 3 +++ {logging => packages/logging}/lib/src/extension.dart | 0 .../logging}/lib/src/logging_integration.dart | 0 {logging => packages/logging}/lib/src/version.dart | 0 {logging => packages/logging}/pubspec.yaml | 0 {logging => packages/logging}/pubspec_overrides.yaml | 0 {logging => packages/logging}/test/extension_test.dart | 0 .../logging}/test/logging_integration_test.dart | 0 {logging => packages/logging}/test/mock_hub.dart | 2 +- .../logging}/test/no_such_method_provider.dart | 0 {logging => packages/logging}/test/version_test.dart | 0 22 files changed, 10 insertions(+), 10 deletions(-) delete mode 120000 logging/CHANGELOG.md delete mode 100644 logging/lib/sentry_logging.dart rename {logging => packages/logging}/.gitignore (100%) create mode 120000 packages/logging/CHANGELOG.md rename {logging => packages/logging}/LICENSE (100%) rename {logging => packages/logging}/README.md (100%) rename {logging => packages/logging}/analysis_options.yaml (100%) rename {logging => packages/logging}/class-diagram.svg (100%) rename {logging => packages/logging}/dartdoc_options.yaml (100%) rename {logging => packages/logging}/example/sentry_logging_example.dart (100%) create mode 100644 packages/logging/lib/sentry_logging.dart rename {logging => packages/logging}/lib/src/extension.dart (100%) rename {logging => packages/logging}/lib/src/logging_integration.dart (100%) rename {logging => packages/logging}/lib/src/version.dart (100%) rename {logging => packages/logging}/pubspec.yaml (100%) rename {logging => packages/logging}/pubspec_overrides.yaml (100%) rename {logging => packages/logging}/test/extension_test.dart (100%) rename {logging => packages/logging}/test/logging_integration_test.dart (100%) rename {logging => packages/logging}/test/mock_hub.dart (95%) rename {logging => packages/logging}/test/no_such_method_provider.dart (100%) rename {logging => packages/logging}/test/version_test.dart (100%) diff --git a/.github/workflows/logging.yml b/.github/workflows/logging.yml index f5149043f2..300605ea29 100644 --- a/.github/workflows/logging.yml +++ b/.github/workflows/logging.yml @@ -12,8 +12,8 @@ on: - '.github/workflows/analyze.yml' - '.github/actions/dart-test/**' - '.github/actions/coverage/**' - - 'dart/**' - - 'logging/**' + - 'packages/dart/**' + - 'packages/logging/**' jobs: cancel-previous-workflow: @@ -39,17 +39,17 @@ jobs: - uses: ./.github/actions/dart-test with: - directory: logging + directory: packages/logging - uses: ./.github/actions/coverage if: runner.os == 'Linux' && matrix.sdk == 'stable' with: token: ${{ secrets.CODECOV_TOKEN }} - directory: logging + directory: packages/logging coverage: sentry_logging min-coverage: 90 analyze: uses: ./.github/workflows/analyze.yml with: - package: logging + package: packages/logging diff --git a/logging/CHANGELOG.md b/logging/CHANGELOG.md deleted file mode 120000 index 04c99a55ca..0000000000 --- a/logging/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../CHANGELOG.md \ No newline at end of file diff --git a/logging/lib/sentry_logging.dart b/logging/lib/sentry_logging.dart deleted file mode 100644 index 236f075583..0000000000 --- a/logging/lib/sentry_logging.dart +++ /dev/null @@ -1,3 +0,0 @@ -library; - -export 'src/logging_integration.dart'; diff --git a/logging/.gitignore b/packages/logging/.gitignore similarity index 100% rename from logging/.gitignore rename to packages/logging/.gitignore diff --git a/packages/logging/CHANGELOG.md b/packages/logging/CHANGELOG.md new file mode 120000 index 0000000000..699cc9e7b7 --- /dev/null +++ b/packages/logging/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/logging/LICENSE b/packages/logging/LICENSE similarity index 100% rename from logging/LICENSE rename to packages/logging/LICENSE diff --git a/logging/README.md b/packages/logging/README.md similarity index 100% rename from logging/README.md rename to packages/logging/README.md diff --git a/logging/analysis_options.yaml b/packages/logging/analysis_options.yaml similarity index 100% rename from logging/analysis_options.yaml rename to packages/logging/analysis_options.yaml diff --git a/logging/class-diagram.svg b/packages/logging/class-diagram.svg similarity index 100% rename from logging/class-diagram.svg rename to packages/logging/class-diagram.svg diff --git a/logging/dartdoc_options.yaml b/packages/logging/dartdoc_options.yaml similarity index 100% rename from logging/dartdoc_options.yaml rename to packages/logging/dartdoc_options.yaml diff --git a/logging/example/sentry_logging_example.dart b/packages/logging/example/sentry_logging_example.dart similarity index 100% rename from logging/example/sentry_logging_example.dart rename to packages/logging/example/sentry_logging_example.dart diff --git a/packages/logging/lib/sentry_logging.dart b/packages/logging/lib/sentry_logging.dart new file mode 100644 index 0000000000..bdf8535dba --- /dev/null +++ b/packages/logging/lib/sentry_logging.dart @@ -0,0 +1,3 @@ +library; + +export '../../../logging/lib/src/logging_integration.dart'; diff --git a/logging/lib/src/extension.dart b/packages/logging/lib/src/extension.dart similarity index 100% rename from logging/lib/src/extension.dart rename to packages/logging/lib/src/extension.dart diff --git a/logging/lib/src/logging_integration.dart b/packages/logging/lib/src/logging_integration.dart similarity index 100% rename from logging/lib/src/logging_integration.dart rename to packages/logging/lib/src/logging_integration.dart diff --git a/logging/lib/src/version.dart b/packages/logging/lib/src/version.dart similarity index 100% rename from logging/lib/src/version.dart rename to packages/logging/lib/src/version.dart diff --git a/logging/pubspec.yaml b/packages/logging/pubspec.yaml similarity index 100% rename from logging/pubspec.yaml rename to packages/logging/pubspec.yaml diff --git a/logging/pubspec_overrides.yaml b/packages/logging/pubspec_overrides.yaml similarity index 100% rename from logging/pubspec_overrides.yaml rename to packages/logging/pubspec_overrides.yaml diff --git a/logging/test/extension_test.dart b/packages/logging/test/extension_test.dart similarity index 100% rename from logging/test/extension_test.dart rename to packages/logging/test/extension_test.dart diff --git a/logging/test/logging_integration_test.dart b/packages/logging/test/logging_integration_test.dart similarity index 100% rename from logging/test/logging_integration_test.dart rename to packages/logging/test/logging_integration_test.dart diff --git a/logging/test/mock_hub.dart b/packages/logging/test/mock_hub.dart similarity index 95% rename from logging/test/mock_hub.dart rename to packages/logging/test/mock_hub.dart index ec3d8b2af9..6fa9714ddc 100644 --- a/logging/test/mock_hub.dart +++ b/packages/logging/test/mock_hub.dart @@ -2,7 +2,7 @@ import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; -import 'no_such_method_provider.dart'; +import '../../../logging/test/no_such_method_provider.dart'; final fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; diff --git a/logging/test/no_such_method_provider.dart b/packages/logging/test/no_such_method_provider.dart similarity index 100% rename from logging/test/no_such_method_provider.dart rename to packages/logging/test/no_such_method_provider.dart diff --git a/logging/test/version_test.dart b/packages/logging/test/version_test.dart similarity index 100% rename from logging/test/version_test.dart rename to packages/logging/test/version_test.dart From 1d983dd656c810b5ca17b91ea280b83b400c8e95 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:01:00 +0200 Subject: [PATCH 12/45] Update --- packages/logging/dartdoc_options.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/logging/dartdoc_options.yaml b/packages/logging/dartdoc_options.yaml index fcb0f9cc26..7cbb8c0d74 120000 --- a/packages/logging/dartdoc_options.yaml +++ b/packages/logging/dartdoc_options.yaml @@ -1 +1 @@ -../packages/dart/dartdoc_options.yaml \ No newline at end of file +../dart/dartdoc_options.yaml \ No newline at end of file From 831a6de09cc675b74c79821c832bdb155b469360 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:03:30 +0200 Subject: [PATCH 13/45] Update --- packages/logging/dartdoc_options.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/logging/dartdoc_options.yaml b/packages/logging/dartdoc_options.yaml index fcb0f9cc26..7cbb8c0d74 120000 --- a/packages/logging/dartdoc_options.yaml +++ b/packages/logging/dartdoc_options.yaml @@ -1 +1 @@ -../packages/dart/dartdoc_options.yaml \ No newline at end of file +../dart/dartdoc_options.yaml \ No newline at end of file From 412240d794f985a5770dc0cace6f784a2efeb433 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:06:38 +0200 Subject: [PATCH 14/45] Update --- packages/logging/lib/sentry_logging.dart | 2 +- packages/logging/test/mock_hub.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/logging/lib/sentry_logging.dart b/packages/logging/lib/sentry_logging.dart index bdf8535dba..236f075583 100644 --- a/packages/logging/lib/sentry_logging.dart +++ b/packages/logging/lib/sentry_logging.dart @@ -1,3 +1,3 @@ library; -export '../../../logging/lib/src/logging_integration.dart'; +export 'src/logging_integration.dart'; diff --git a/packages/logging/test/mock_hub.dart b/packages/logging/test/mock_hub.dart index 6fa9714ddc..ec3d8b2af9 100644 --- a/packages/logging/test/mock_hub.dart +++ b/packages/logging/test/mock_hub.dart @@ -2,7 +2,7 @@ import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; -import '../../../logging/test/no_such_method_provider.dart'; +import 'no_such_method_provider.dart'; final fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; From 26ec9b8160accf7e44ade4af8a82672239548a72 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:09:19 +0200 Subject: [PATCH 15/45] Update --- packages/logging/pubspec_overrides.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/logging/pubspec_overrides.yaml b/packages/logging/pubspec_overrides.yaml index 0a17afda20..04ac16fe75 100644 --- a/packages/logging/pubspec_overrides.yaml +++ b/packages/logging/pubspec_overrides.yaml @@ -1,3 +1,4 @@ +# melos_managed_dependency_overrides: sentry dependency_overrides: sentry: - path: ../packages/dart + path: ../dart From 33a62262f7e2ddac24bd6fe83f4e1caf1c9a14a3 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:16:07 +0200 Subject: [PATCH 16/45] Add dio to packages --- dio/CHANGELOG.md | 1 - dio/dartdoc_options.yaml | 1 - {dio => packages/dio}/.gitignore | 0 packages/dio/CHANGELOG.md | 1 + {dio => packages/dio}/LICENSE | 0 {dio => packages/dio}/README.md | 0 {dio => packages/dio}/analysis_options.yaml | 0 {dio => packages/dio}/class-diagram.svg | 0 packages/dio/dartdoc_options.yaml | 1 + {dio => packages/dio}/example/example.dart | 0 {dio => packages/dio}/lib/sentry_dio.dart | 4 ++-- .../dio}/lib/src/breadcrumb_client_adapter.dart | 0 {dio => packages/dio}/lib/src/dio_error_extractor.dart | 0 {dio => packages/dio}/lib/src/dio_event_processor.dart | 0 .../dio}/lib/src/dio_stacktrace_extractor.dart | 0 .../dio}/lib/src/failed_request_interceptor.dart | 0 .../dio}/lib/src/sentry_dio_client_adapter.dart | 4 ++-- {dio => packages/dio}/lib/src/sentry_dio_extension.dart | 6 +++--- {dio => packages/dio}/lib/src/sentry_transformer.dart | 0 {dio => packages/dio}/lib/src/tracing_client_adapter.dart | 0 {dio => packages/dio}/lib/src/version.dart | 0 {dio => packages/dio}/pubspec.yaml | 0 {dio => packages/dio}/pubspec_overrides.yaml | 0 .../dio}/test/breadcrumb_client_adapter_test.dart | 4 ++-- {dio => packages/dio}/test/dio_error_extractor_test.dart | 0 {dio => packages/dio}/test/dio_event_processor_test.dart | 2 +- .../dio}/test/dio_stacktrace_extractor_test.dart | 0 .../dio}/test/failed_request_interceptor_test.dart | 4 ++-- {dio => packages/dio}/test/mocks.dart | 0 .../dio}/test/mocks/mock_http_client_adapter.dart | 0 {dio => packages/dio}/test/mocks/mock_hub.dart | 0 {dio => packages/dio}/test/mocks/mock_transport.dart | 0 .../dio}/test/mocks/no_such_method_provider.dart | 0 .../dio}/test/sentry_dio_client_adapter_test.dart | 8 ++++---- {dio => packages/dio}/test/sentry_dio_extension_test.dart | 2 +- {dio => packages/dio}/test/sentry_transformer_test.dart | 4 ++-- .../dio}/test/tracing_client_adapter_test.dart | 6 +++--- {dio => packages/dio}/test/version_test.dart | 0 38 files changed, 24 insertions(+), 24 deletions(-) delete mode 120000 dio/CHANGELOG.md delete mode 120000 dio/dartdoc_options.yaml rename {dio => packages/dio}/.gitignore (100%) create mode 120000 packages/dio/CHANGELOG.md rename {dio => packages/dio}/LICENSE (100%) rename {dio => packages/dio}/README.md (100%) rename {dio => packages/dio}/analysis_options.yaml (100%) rename {dio => packages/dio}/class-diagram.svg (100%) create mode 120000 packages/dio/dartdoc_options.yaml rename {dio => packages/dio}/example/example.dart (100%) rename {dio => packages/dio}/lib/sentry_dio.dart (52%) rename {dio => packages/dio}/lib/src/breadcrumb_client_adapter.dart (100%) rename {dio => packages/dio}/lib/src/dio_error_extractor.dart (100%) rename {dio => packages/dio}/lib/src/dio_event_processor.dart (100%) rename {dio => packages/dio}/lib/src/dio_stacktrace_extractor.dart (100%) rename {dio => packages/dio}/lib/src/failed_request_interceptor.dart (100%) rename {dio => packages/dio}/lib/src/sentry_dio_client_adapter.dart (93%) rename {dio => packages/dio}/lib/src/sentry_dio_extension.dart (95%) rename {dio => packages/dio}/lib/src/sentry_transformer.dart (100%) rename {dio => packages/dio}/lib/src/tracing_client_adapter.dart (100%) rename {dio => packages/dio}/lib/src/version.dart (100%) rename {dio => packages/dio}/pubspec.yaml (100%) rename {dio => packages/dio}/pubspec_overrides.yaml (100%) rename {dio => packages/dio}/test/breadcrumb_client_adapter_test.dart (98%) rename {dio => packages/dio}/test/dio_error_extractor_test.dart (100%) rename {dio => packages/dio}/test/dio_event_processor_test.dart (99%) rename {dio => packages/dio}/test/dio_stacktrace_extractor_test.dart (100%) rename {dio => packages/dio}/test/failed_request_interceptor_test.dart (98%) rename {dio => packages/dio}/test/mocks.dart (100%) rename {dio => packages/dio}/test/mocks/mock_http_client_adapter.dart (100%) rename {dio => packages/dio}/test/mocks/mock_hub.dart (100%) rename {dio => packages/dio}/test/mocks/mock_transport.dart (100%) rename {dio => packages/dio}/test/mocks/no_such_method_provider.dart (100%) rename {dio => packages/dio}/test/sentry_dio_client_adapter_test.dart (95%) rename {dio => packages/dio}/test/sentry_dio_extension_test.dart (99%) rename {dio => packages/dio}/test/sentry_transformer_test.dart (98%) rename {dio => packages/dio}/test/tracing_client_adapter_test.dart (98%) rename {dio => packages/dio}/test/version_test.dart (100%) diff --git a/dio/CHANGELOG.md b/dio/CHANGELOG.md deleted file mode 120000 index 04c99a55ca..0000000000 --- a/dio/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../CHANGELOG.md \ No newline at end of file diff --git a/dio/dartdoc_options.yaml b/dio/dartdoc_options.yaml deleted file mode 120000 index fcb0f9cc26..0000000000 --- a/dio/dartdoc_options.yaml +++ /dev/null @@ -1 +0,0 @@ -../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/dio/.gitignore b/packages/dio/.gitignore similarity index 100% rename from dio/.gitignore rename to packages/dio/.gitignore diff --git a/packages/dio/CHANGELOG.md b/packages/dio/CHANGELOG.md new file mode 120000 index 0000000000..699cc9e7b7 --- /dev/null +++ b/packages/dio/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/dio/LICENSE b/packages/dio/LICENSE similarity index 100% rename from dio/LICENSE rename to packages/dio/LICENSE diff --git a/dio/README.md b/packages/dio/README.md similarity index 100% rename from dio/README.md rename to packages/dio/README.md diff --git a/dio/analysis_options.yaml b/packages/dio/analysis_options.yaml similarity index 100% rename from dio/analysis_options.yaml rename to packages/dio/analysis_options.yaml diff --git a/dio/class-diagram.svg b/packages/dio/class-diagram.svg similarity index 100% rename from dio/class-diagram.svg rename to packages/dio/class-diagram.svg diff --git a/packages/dio/dartdoc_options.yaml b/packages/dio/dartdoc_options.yaml new file mode 120000 index 0000000000..7cbb8c0d74 --- /dev/null +++ b/packages/dio/dartdoc_options.yaml @@ -0,0 +1 @@ +../dart/dartdoc_options.yaml \ No newline at end of file diff --git a/dio/example/example.dart b/packages/dio/example/example.dart similarity index 100% rename from dio/example/example.dart rename to packages/dio/example/example.dart diff --git a/dio/lib/sentry_dio.dart b/packages/dio/lib/sentry_dio.dart similarity index 52% rename from dio/lib/sentry_dio.dart rename to packages/dio/lib/sentry_dio.dart index d0238e4c10..f11674cf8a 100644 --- a/dio/lib/sentry_dio.dart +++ b/packages/dio/lib/sentry_dio.dart @@ -1,7 +1,7 @@ library; -export 'src/sentry_dio_extension.dart'; +export '../../../dio/lib/src/sentry_dio_extension.dart'; // Export the processor in case people want to use it standalone. // Normally one doesn't need to use it directly. -export 'src/dio_event_processor.dart'; +export '../../../dio/lib/src/dio_event_processor.dart'; diff --git a/dio/lib/src/breadcrumb_client_adapter.dart b/packages/dio/lib/src/breadcrumb_client_adapter.dart similarity index 100% rename from dio/lib/src/breadcrumb_client_adapter.dart rename to packages/dio/lib/src/breadcrumb_client_adapter.dart diff --git a/dio/lib/src/dio_error_extractor.dart b/packages/dio/lib/src/dio_error_extractor.dart similarity index 100% rename from dio/lib/src/dio_error_extractor.dart rename to packages/dio/lib/src/dio_error_extractor.dart diff --git a/dio/lib/src/dio_event_processor.dart b/packages/dio/lib/src/dio_event_processor.dart similarity index 100% rename from dio/lib/src/dio_event_processor.dart rename to packages/dio/lib/src/dio_event_processor.dart diff --git a/dio/lib/src/dio_stacktrace_extractor.dart b/packages/dio/lib/src/dio_stacktrace_extractor.dart similarity index 100% rename from dio/lib/src/dio_stacktrace_extractor.dart rename to packages/dio/lib/src/dio_stacktrace_extractor.dart diff --git a/dio/lib/src/failed_request_interceptor.dart b/packages/dio/lib/src/failed_request_interceptor.dart similarity index 100% rename from dio/lib/src/failed_request_interceptor.dart rename to packages/dio/lib/src/failed_request_interceptor.dart diff --git a/dio/lib/src/sentry_dio_client_adapter.dart b/packages/dio/lib/src/sentry_dio_client_adapter.dart similarity index 93% rename from dio/lib/src/sentry_dio_client_adapter.dart rename to packages/dio/lib/src/sentry_dio_client_adapter.dart index 46b6f49fca..28f8d38728 100644 --- a/dio/lib/src/sentry_dio_client_adapter.dart +++ b/packages/dio/lib/src/sentry_dio_client_adapter.dart @@ -5,8 +5,8 @@ import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:sentry/sentry.dart'; -import 'breadcrumb_client_adapter.dart'; -import 'tracing_client_adapter.dart'; +import '../../../../dio/lib/src/breadcrumb_client_adapter.dart'; +import '../../../../dio/lib/src/tracing_client_adapter.dart'; /// A [Dio](https://pub.dev/packages/dio)-package compatible HTTP client adapter. /// diff --git a/dio/lib/src/sentry_dio_extension.dart b/packages/dio/lib/src/sentry_dio_extension.dart similarity index 95% rename from dio/lib/src/sentry_dio_extension.dart rename to packages/dio/lib/src/sentry_dio_extension.dart index d3c2a86dca..cc56452f0d 100644 --- a/dio/lib/src/sentry_dio_extension.dart +++ b/packages/dio/lib/src/sentry_dio_extension.dart @@ -2,11 +2,11 @@ import 'package:dio/dio.dart'; import 'package:sentry/sentry.dart'; -import 'dio_error_extractor.dart'; -import 'dio_event_processor.dart'; +import '../../../../dio/lib/src/dio_error_extractor.dart'; +import '../../../../dio/lib/src/dio_event_processor.dart'; import 'dio_stacktrace_extractor.dart'; import 'failed_request_interceptor.dart'; -import 'sentry_transformer.dart'; +import '../../../../dio/lib/src/sentry_transformer.dart'; import 'sentry_dio_client_adapter.dart'; import 'version.dart'; diff --git a/dio/lib/src/sentry_transformer.dart b/packages/dio/lib/src/sentry_transformer.dart similarity index 100% rename from dio/lib/src/sentry_transformer.dart rename to packages/dio/lib/src/sentry_transformer.dart diff --git a/dio/lib/src/tracing_client_adapter.dart b/packages/dio/lib/src/tracing_client_adapter.dart similarity index 100% rename from dio/lib/src/tracing_client_adapter.dart rename to packages/dio/lib/src/tracing_client_adapter.dart diff --git a/dio/lib/src/version.dart b/packages/dio/lib/src/version.dart similarity index 100% rename from dio/lib/src/version.dart rename to packages/dio/lib/src/version.dart diff --git a/dio/pubspec.yaml b/packages/dio/pubspec.yaml similarity index 100% rename from dio/pubspec.yaml rename to packages/dio/pubspec.yaml diff --git a/dio/pubspec_overrides.yaml b/packages/dio/pubspec_overrides.yaml similarity index 100% rename from dio/pubspec_overrides.yaml rename to packages/dio/pubspec_overrides.yaml diff --git a/dio/test/breadcrumb_client_adapter_test.dart b/packages/dio/test/breadcrumb_client_adapter_test.dart similarity index 98% rename from dio/test/breadcrumb_client_adapter_test.dart rename to packages/dio/test/breadcrumb_client_adapter_test.dart index 885d694b01..fc5a643516 100644 --- a/dio/test/breadcrumb_client_adapter_test.dart +++ b/packages/dio/test/breadcrumb_client_adapter_test.dart @@ -6,8 +6,8 @@ import 'package:sentry/sentry.dart'; import 'package:sentry_dio/src/breadcrumb_client_adapter.dart'; import 'package:test/test.dart'; -import 'mocks/mock_http_client_adapter.dart'; -import 'mocks/mock_hub.dart'; +import '../../../dio/test/mocks/mock_http_client_adapter.dart'; +import '../../../dio/test/mocks/mock_hub.dart'; void main() { group(BreadcrumbClientAdapter, () { diff --git a/dio/test/dio_error_extractor_test.dart b/packages/dio/test/dio_error_extractor_test.dart similarity index 100% rename from dio/test/dio_error_extractor_test.dart rename to packages/dio/test/dio_error_extractor_test.dart diff --git a/dio/test/dio_event_processor_test.dart b/packages/dio/test/dio_event_processor_test.dart similarity index 99% rename from dio/test/dio_event_processor_test.dart rename to packages/dio/test/dio_event_processor_test.dart index d5878b04cc..98a2bab8da 100644 --- a/dio/test/dio_event_processor_test.dart +++ b/packages/dio/test/dio_event_processor_test.dart @@ -7,7 +7,7 @@ import 'package:sentry_dio/src/dio_error_extractor.dart'; import 'package:test/test.dart'; import 'package:sentry/src/sentry_exception_factory.dart'; -import 'mocks.dart'; +import '../../../dio/test/mocks.dart'; void main() { late Fixture fixture; diff --git a/dio/test/dio_stacktrace_extractor_test.dart b/packages/dio/test/dio_stacktrace_extractor_test.dart similarity index 100% rename from dio/test/dio_stacktrace_extractor_test.dart rename to packages/dio/test/dio_stacktrace_extractor_test.dart diff --git a/dio/test/failed_request_interceptor_test.dart b/packages/dio/test/failed_request_interceptor_test.dart similarity index 98% rename from dio/test/failed_request_interceptor_test.dart rename to packages/dio/test/failed_request_interceptor_test.dart index 4602180e0c..b6ded82ae4 100644 --- a/dio/test/failed_request_interceptor_test.dart +++ b/packages/dio/test/failed_request_interceptor_test.dart @@ -5,8 +5,8 @@ import 'package:sentry/sentry.dart'; import 'package:sentry_dio/src/failed_request_interceptor.dart'; import 'package:test/test.dart'; -import 'mocks/mock_hub.dart'; -import 'mocks/no_such_method_provider.dart'; +import '../../../dio/test/mocks/mock_hub.dart'; +import '../../../dio/test/mocks/no_such_method_provider.dart'; void main() { late Fixture fixture; diff --git a/dio/test/mocks.dart b/packages/dio/test/mocks.dart similarity index 100% rename from dio/test/mocks.dart rename to packages/dio/test/mocks.dart diff --git a/dio/test/mocks/mock_http_client_adapter.dart b/packages/dio/test/mocks/mock_http_client_adapter.dart similarity index 100% rename from dio/test/mocks/mock_http_client_adapter.dart rename to packages/dio/test/mocks/mock_http_client_adapter.dart diff --git a/dio/test/mocks/mock_hub.dart b/packages/dio/test/mocks/mock_hub.dart similarity index 100% rename from dio/test/mocks/mock_hub.dart rename to packages/dio/test/mocks/mock_hub.dart diff --git a/dio/test/mocks/mock_transport.dart b/packages/dio/test/mocks/mock_transport.dart similarity index 100% rename from dio/test/mocks/mock_transport.dart rename to packages/dio/test/mocks/mock_transport.dart diff --git a/dio/test/mocks/no_such_method_provider.dart b/packages/dio/test/mocks/no_such_method_provider.dart similarity index 100% rename from dio/test/mocks/no_such_method_provider.dart rename to packages/dio/test/mocks/no_such_method_provider.dart diff --git a/dio/test/sentry_dio_client_adapter_test.dart b/packages/dio/test/sentry_dio_client_adapter_test.dart similarity index 95% rename from dio/test/sentry_dio_client_adapter_test.dart rename to packages/dio/test/sentry_dio_client_adapter_test.dart index 1ecd3cda5b..87f871f6dd 100644 --- a/dio/test/sentry_dio_client_adapter_test.dart +++ b/packages/dio/test/sentry_dio_client_adapter_test.dart @@ -6,10 +6,10 @@ import 'package:sentry/src/sentry_tracer.dart'; import 'package:sentry_dio/src/sentry_dio_client_adapter.dart'; import 'package:test/test.dart'; -import 'mocks.dart'; -import 'mocks/mock_http_client_adapter.dart'; -import 'mocks/mock_hub.dart'; -import 'mocks/mock_transport.dart'; +import '../../../dio/test/mocks.dart'; +import '../../../dio/test/mocks/mock_http_client_adapter.dart'; +import '../../../dio/test/mocks/mock_hub.dart'; +import '../../../dio/test/mocks/mock_transport.dart'; final requestUri = Uri.parse('https://example.com/'); diff --git a/dio/test/sentry_dio_extension_test.dart b/packages/dio/test/sentry_dio_extension_test.dart similarity index 99% rename from dio/test/sentry_dio_extension_test.dart rename to packages/dio/test/sentry_dio_extension_test.dart index fe5787977f..4f8ffd6d19 100644 --- a/dio/test/sentry_dio_extension_test.dart +++ b/packages/dio/test/sentry_dio_extension_test.dart @@ -12,7 +12,7 @@ import 'package:sentry_dio/src/version.dart'; import 'package:test/test.dart'; import 'failed_request_interceptor_test.dart'; -import 'mocks/mock_hub.dart'; +import '../../../dio/test/mocks/mock_hub.dart'; void main() { late Fixture fixture; diff --git a/dio/test/sentry_transformer_test.dart b/packages/dio/test/sentry_transformer_test.dart similarity index 98% rename from dio/test/sentry_transformer_test.dart rename to packages/dio/test/sentry_transformer_test.dart index 93fd36f138..d4cb2f4e4b 100644 --- a/dio/test/sentry_transformer_test.dart +++ b/packages/dio/test/sentry_transformer_test.dart @@ -7,8 +7,8 @@ import 'package:sentry_dio/src/sentry_transformer.dart'; import 'package:test/scaffolding.dart'; import 'package:test/test.dart'; -import 'mocks.dart'; -import 'mocks/mock_transport.dart'; +import '../../../dio/test/mocks.dart'; +import '../../../dio/test/mocks/mock_transport.dart'; import 'package:sentry/src/sentry_tracer.dart'; final requestUri = Uri.parse('https://example.com?foo=bar#baz'); diff --git a/dio/test/tracing_client_adapter_test.dart b/packages/dio/test/tracing_client_adapter_test.dart similarity index 98% rename from dio/test/tracing_client_adapter_test.dart rename to packages/dio/test/tracing_client_adapter_test.dart index a991d6c7f1..f306e68ba1 100644 --- a/dio/test/tracing_client_adapter_test.dart +++ b/packages/dio/test/tracing_client_adapter_test.dart @@ -7,9 +7,9 @@ import 'package:sentry/src/sentry_tracer.dart'; import 'package:sentry_dio/src/tracing_client_adapter.dart'; import 'package:test/test.dart'; -import 'mocks.dart'; -import 'mocks/mock_http_client_adapter.dart'; -import 'mocks/mock_transport.dart'; +import '../../../dio/test/mocks.dart'; +import '../../../dio/test/mocks/mock_http_client_adapter.dart'; +import '../../../dio/test/mocks/mock_transport.dart'; final requestUri = Uri.parse('https://example.com?foo=bar#baz'); final requestOptions = '?foo=bar#baz'; diff --git a/dio/test/version_test.dart b/packages/dio/test/version_test.dart similarity index 100% rename from dio/test/version_test.dart rename to packages/dio/test/version_test.dart From f0b3ecf47f8655fc18474f4f837d0699910375df Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:16:47 +0200 Subject: [PATCH 17/45] Update workflow --- .github/workflows/dio.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dio.yml b/.github/workflows/dio.yml index ec461025a1..a2de24b88d 100644 --- a/.github/workflows/dio.yml +++ b/.github/workflows/dio.yml @@ -12,8 +12,8 @@ on: - '.github/workflows/analyze.yml' - '.github/actions/dart-test/**' - '.github/actions/coverage/**' - - 'dart/**' - - 'dio/**' + - 'packages/dart/**' + - 'packages/dio/**' jobs: cancel-previous-workflow: @@ -39,17 +39,17 @@ jobs: - uses: ./.github/actions/dart-test with: - directory: dio + directory: packages/dio - uses: ./.github/actions/coverage if: runner.os == 'Linux' && matrix.sdk == 'stable' with: token: ${{ secrets.CODECOV_TOKEN }} - directory: dio + directory: packages/dio coverage: sentry_dio min-coverage: 81 analyze: uses: ./.github/workflows/analyze.yml with: - package: dio + package: packages/dio From 17289a577e331779d2d55b463bc14592979e8593 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:19:19 +0200 Subject: [PATCH 18/45] Update --- packages/dio/lib/src/sentry_dio_client_adapter.dart | 4 ++-- packages/dio/lib/src/sentry_dio_extension.dart | 6 +++--- packages/dio/test/breadcrumb_client_adapter_test.dart | 4 ++-- packages/dio/test/dio_event_processor_test.dart | 2 +- packages/dio/test/failed_request_interceptor_test.dart | 4 ++-- packages/dio/test/sentry_dio_client_adapter_test.dart | 8 ++++---- packages/dio/test/sentry_dio_extension_test.dart | 2 +- packages/dio/test/sentry_transformer_test.dart | 4 ++-- packages/dio/test/tracing_client_adapter_test.dart | 6 +++--- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/dio/lib/src/sentry_dio_client_adapter.dart b/packages/dio/lib/src/sentry_dio_client_adapter.dart index 28f8d38728..69997c888d 100644 --- a/packages/dio/lib/src/sentry_dio_client_adapter.dart +++ b/packages/dio/lib/src/sentry_dio_client_adapter.dart @@ -5,8 +5,8 @@ import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:sentry/sentry.dart'; -import '../../../../dio/lib/src/breadcrumb_client_adapter.dart'; -import '../../../../dio/lib/src/tracing_client_adapter.dart'; +import 'dio/lib/src/breadcrumb_client_adapter.dart'; +import 'dio/lib/src/tracing_client_adapter.dart'; /// A [Dio](https://pub.dev/packages/dio)-package compatible HTTP client adapter. /// diff --git a/packages/dio/lib/src/sentry_dio_extension.dart b/packages/dio/lib/src/sentry_dio_extension.dart index cc56452f0d..c87b8795b5 100644 --- a/packages/dio/lib/src/sentry_dio_extension.dart +++ b/packages/dio/lib/src/sentry_dio_extension.dart @@ -2,11 +2,11 @@ import 'package:dio/dio.dart'; import 'package:sentry/sentry.dart'; -import '../../../../dio/lib/src/dio_error_extractor.dart'; -import '../../../../dio/lib/src/dio_event_processor.dart'; +import 'dio/lib/src/dio_error_extractor.dart'; +import 'dio/lib/src/dio_event_processor.dart'; import 'dio_stacktrace_extractor.dart'; import 'failed_request_interceptor.dart'; -import '../../../../dio/lib/src/sentry_transformer.dart'; +import 'dio/lib/src/sentry_transformer.dart'; import 'sentry_dio_client_adapter.dart'; import 'version.dart'; diff --git a/packages/dio/test/breadcrumb_client_adapter_test.dart b/packages/dio/test/breadcrumb_client_adapter_test.dart index fc5a643516..4c47cc9987 100644 --- a/packages/dio/test/breadcrumb_client_adapter_test.dart +++ b/packages/dio/test/breadcrumb_client_adapter_test.dart @@ -6,8 +6,8 @@ import 'package:sentry/sentry.dart'; import 'package:sentry_dio/src/breadcrumb_client_adapter.dart'; import 'package:test/test.dart'; -import '../../../dio/test/mocks/mock_http_client_adapter.dart'; -import '../../../dio/test/mocks/mock_hub.dart'; +import 'dio/test/mocks/mock_http_client_adapter.dart'; +import 'dio/test/mocks/mock_hub.dart'; void main() { group(BreadcrumbClientAdapter, () { diff --git a/packages/dio/test/dio_event_processor_test.dart b/packages/dio/test/dio_event_processor_test.dart index 98a2bab8da..947fb90ffa 100644 --- a/packages/dio/test/dio_event_processor_test.dart +++ b/packages/dio/test/dio_event_processor_test.dart @@ -7,7 +7,7 @@ import 'package:sentry_dio/src/dio_error_extractor.dart'; import 'package:test/test.dart'; import 'package:sentry/src/sentry_exception_factory.dart'; -import '../../../dio/test/mocks.dart'; +import 'dio/test/mocks.dart'; void main() { late Fixture fixture; diff --git a/packages/dio/test/failed_request_interceptor_test.dart b/packages/dio/test/failed_request_interceptor_test.dart index b6ded82ae4..1b1b819e74 100644 --- a/packages/dio/test/failed_request_interceptor_test.dart +++ b/packages/dio/test/failed_request_interceptor_test.dart @@ -5,8 +5,8 @@ import 'package:sentry/sentry.dart'; import 'package:sentry_dio/src/failed_request_interceptor.dart'; import 'package:test/test.dart'; -import '../../../dio/test/mocks/mock_hub.dart'; -import '../../../dio/test/mocks/no_such_method_provider.dart'; +import 'dio/test/mocks/mock_hub.dart'; +import 'dio/test/mocks/no_such_method_provider.dart'; void main() { late Fixture fixture; diff --git a/packages/dio/test/sentry_dio_client_adapter_test.dart b/packages/dio/test/sentry_dio_client_adapter_test.dart index 87f871f6dd..6e24583ff9 100644 --- a/packages/dio/test/sentry_dio_client_adapter_test.dart +++ b/packages/dio/test/sentry_dio_client_adapter_test.dart @@ -6,10 +6,10 @@ import 'package:sentry/src/sentry_tracer.dart'; import 'package:sentry_dio/src/sentry_dio_client_adapter.dart'; import 'package:test/test.dart'; -import '../../../dio/test/mocks.dart'; -import '../../../dio/test/mocks/mock_http_client_adapter.dart'; -import '../../../dio/test/mocks/mock_hub.dart'; -import '../../../dio/test/mocks/mock_transport.dart'; +import 'dio/test/mocks.dart'; +import 'dio/test/mocks/mock_http_client_adapter.dart'; +import 'dio/test/mocks/mock_hub.dart'; +import 'dio/test/mocks/mock_transport.dart'; final requestUri = Uri.parse('https://example.com/'); diff --git a/packages/dio/test/sentry_dio_extension_test.dart b/packages/dio/test/sentry_dio_extension_test.dart index 4f8ffd6d19..d3caa9a27f 100644 --- a/packages/dio/test/sentry_dio_extension_test.dart +++ b/packages/dio/test/sentry_dio_extension_test.dart @@ -12,7 +12,7 @@ import 'package:sentry_dio/src/version.dart'; import 'package:test/test.dart'; import 'failed_request_interceptor_test.dart'; -import '../../../dio/test/mocks/mock_hub.dart'; +import 'dio/test/mocks/mock_hub.dart'; void main() { late Fixture fixture; diff --git a/packages/dio/test/sentry_transformer_test.dart b/packages/dio/test/sentry_transformer_test.dart index d4cb2f4e4b..2199861991 100644 --- a/packages/dio/test/sentry_transformer_test.dart +++ b/packages/dio/test/sentry_transformer_test.dart @@ -7,8 +7,8 @@ import 'package:sentry_dio/src/sentry_transformer.dart'; import 'package:test/scaffolding.dart'; import 'package:test/test.dart'; -import '../../../dio/test/mocks.dart'; -import '../../../dio/test/mocks/mock_transport.dart'; +import 'dio/test/mocks.dart'; +import 'dio/test/mocks/mock_transport.dart'; import 'package:sentry/src/sentry_tracer.dart'; final requestUri = Uri.parse('https://example.com?foo=bar#baz'); diff --git a/packages/dio/test/tracing_client_adapter_test.dart b/packages/dio/test/tracing_client_adapter_test.dart index f306e68ba1..46088b7011 100644 --- a/packages/dio/test/tracing_client_adapter_test.dart +++ b/packages/dio/test/tracing_client_adapter_test.dart @@ -7,9 +7,9 @@ import 'package:sentry/src/sentry_tracer.dart'; import 'package:sentry_dio/src/tracing_client_adapter.dart'; import 'package:test/test.dart'; -import '../../../dio/test/mocks.dart'; -import '../../../dio/test/mocks/mock_http_client_adapter.dart'; -import '../../../dio/test/mocks/mock_transport.dart'; +import 'dio/test/mocks.dart'; +import 'dio/test/mocks/mock_http_client_adapter.dart'; +import 'dio/test/mocks/mock_transport.dart'; final requestUri = Uri.parse('https://example.com?foo=bar#baz'); final requestOptions = '?foo=bar#baz'; From feb0e3f2395e27c283d7d9be588c913c89909f41 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:20:14 +0200 Subject: [PATCH 19/45] Update --- packages/dio/test/breadcrumb_client_adapter_test.dart | 4 ++-- packages/dio/test/dio_event_processor_test.dart | 2 +- packages/dio/test/failed_request_interceptor_test.dart | 4 ++-- packages/dio/test/sentry_dio_client_adapter_test.dart | 8 ++++---- packages/dio/test/sentry_dio_extension_test.dart | 2 +- packages/dio/test/sentry_transformer_test.dart | 4 ++-- packages/dio/test/tracing_client_adapter_test.dart | 6 +++--- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/dio/test/breadcrumb_client_adapter_test.dart b/packages/dio/test/breadcrumb_client_adapter_test.dart index 4c47cc9987..885d694b01 100644 --- a/packages/dio/test/breadcrumb_client_adapter_test.dart +++ b/packages/dio/test/breadcrumb_client_adapter_test.dart @@ -6,8 +6,8 @@ import 'package:sentry/sentry.dart'; import 'package:sentry_dio/src/breadcrumb_client_adapter.dart'; import 'package:test/test.dart'; -import 'dio/test/mocks/mock_http_client_adapter.dart'; -import 'dio/test/mocks/mock_hub.dart'; +import 'mocks/mock_http_client_adapter.dart'; +import 'mocks/mock_hub.dart'; void main() { group(BreadcrumbClientAdapter, () { diff --git a/packages/dio/test/dio_event_processor_test.dart b/packages/dio/test/dio_event_processor_test.dart index 947fb90ffa..d5878b04cc 100644 --- a/packages/dio/test/dio_event_processor_test.dart +++ b/packages/dio/test/dio_event_processor_test.dart @@ -7,7 +7,7 @@ import 'package:sentry_dio/src/dio_error_extractor.dart'; import 'package:test/test.dart'; import 'package:sentry/src/sentry_exception_factory.dart'; -import 'dio/test/mocks.dart'; +import 'mocks.dart'; void main() { late Fixture fixture; diff --git a/packages/dio/test/failed_request_interceptor_test.dart b/packages/dio/test/failed_request_interceptor_test.dart index 1b1b819e74..4602180e0c 100644 --- a/packages/dio/test/failed_request_interceptor_test.dart +++ b/packages/dio/test/failed_request_interceptor_test.dart @@ -5,8 +5,8 @@ import 'package:sentry/sentry.dart'; import 'package:sentry_dio/src/failed_request_interceptor.dart'; import 'package:test/test.dart'; -import 'dio/test/mocks/mock_hub.dart'; -import 'dio/test/mocks/no_such_method_provider.dart'; +import 'mocks/mock_hub.dart'; +import 'mocks/no_such_method_provider.dart'; void main() { late Fixture fixture; diff --git a/packages/dio/test/sentry_dio_client_adapter_test.dart b/packages/dio/test/sentry_dio_client_adapter_test.dart index 6e24583ff9..1ecd3cda5b 100644 --- a/packages/dio/test/sentry_dio_client_adapter_test.dart +++ b/packages/dio/test/sentry_dio_client_adapter_test.dart @@ -6,10 +6,10 @@ import 'package:sentry/src/sentry_tracer.dart'; import 'package:sentry_dio/src/sentry_dio_client_adapter.dart'; import 'package:test/test.dart'; -import 'dio/test/mocks.dart'; -import 'dio/test/mocks/mock_http_client_adapter.dart'; -import 'dio/test/mocks/mock_hub.dart'; -import 'dio/test/mocks/mock_transport.dart'; +import 'mocks.dart'; +import 'mocks/mock_http_client_adapter.dart'; +import 'mocks/mock_hub.dart'; +import 'mocks/mock_transport.dart'; final requestUri = Uri.parse('https://example.com/'); diff --git a/packages/dio/test/sentry_dio_extension_test.dart b/packages/dio/test/sentry_dio_extension_test.dart index d3caa9a27f..fe5787977f 100644 --- a/packages/dio/test/sentry_dio_extension_test.dart +++ b/packages/dio/test/sentry_dio_extension_test.dart @@ -12,7 +12,7 @@ import 'package:sentry_dio/src/version.dart'; import 'package:test/test.dart'; import 'failed_request_interceptor_test.dart'; -import 'dio/test/mocks/mock_hub.dart'; +import 'mocks/mock_hub.dart'; void main() { late Fixture fixture; diff --git a/packages/dio/test/sentry_transformer_test.dart b/packages/dio/test/sentry_transformer_test.dart index 2199861991..93fd36f138 100644 --- a/packages/dio/test/sentry_transformer_test.dart +++ b/packages/dio/test/sentry_transformer_test.dart @@ -7,8 +7,8 @@ import 'package:sentry_dio/src/sentry_transformer.dart'; import 'package:test/scaffolding.dart'; import 'package:test/test.dart'; -import 'dio/test/mocks.dart'; -import 'dio/test/mocks/mock_transport.dart'; +import 'mocks.dart'; +import 'mocks/mock_transport.dart'; import 'package:sentry/src/sentry_tracer.dart'; final requestUri = Uri.parse('https://example.com?foo=bar#baz'); diff --git a/packages/dio/test/tracing_client_adapter_test.dart b/packages/dio/test/tracing_client_adapter_test.dart index 46088b7011..a991d6c7f1 100644 --- a/packages/dio/test/tracing_client_adapter_test.dart +++ b/packages/dio/test/tracing_client_adapter_test.dart @@ -7,9 +7,9 @@ import 'package:sentry/src/sentry_tracer.dart'; import 'package:sentry_dio/src/tracing_client_adapter.dart'; import 'package:test/test.dart'; -import 'dio/test/mocks.dart'; -import 'dio/test/mocks/mock_http_client_adapter.dart'; -import 'dio/test/mocks/mock_transport.dart'; +import 'mocks.dart'; +import 'mocks/mock_http_client_adapter.dart'; +import 'mocks/mock_transport.dart'; final requestUri = Uri.parse('https://example.com?foo=bar#baz'); final requestOptions = '?foo=bar#baz'; From 4bc5a300f39050acc6f86510059cd0f9ac5857a5 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:20:59 +0200 Subject: [PATCH 20/45] Update --- packages/dio/lib/src/sentry_dio_client_adapter.dart | 4 ++-- packages/dio/lib/src/sentry_dio_extension.dart | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/dio/lib/src/sentry_dio_client_adapter.dart b/packages/dio/lib/src/sentry_dio_client_adapter.dart index 69997c888d..46b6f49fca 100644 --- a/packages/dio/lib/src/sentry_dio_client_adapter.dart +++ b/packages/dio/lib/src/sentry_dio_client_adapter.dart @@ -5,8 +5,8 @@ import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:sentry/sentry.dart'; -import 'dio/lib/src/breadcrumb_client_adapter.dart'; -import 'dio/lib/src/tracing_client_adapter.dart'; +import 'breadcrumb_client_adapter.dart'; +import 'tracing_client_adapter.dart'; /// A [Dio](https://pub.dev/packages/dio)-package compatible HTTP client adapter. /// diff --git a/packages/dio/lib/src/sentry_dio_extension.dart b/packages/dio/lib/src/sentry_dio_extension.dart index c87b8795b5..d3c2a86dca 100644 --- a/packages/dio/lib/src/sentry_dio_extension.dart +++ b/packages/dio/lib/src/sentry_dio_extension.dart @@ -2,11 +2,11 @@ import 'package:dio/dio.dart'; import 'package:sentry/sentry.dart'; -import 'dio/lib/src/dio_error_extractor.dart'; -import 'dio/lib/src/dio_event_processor.dart'; +import 'dio_error_extractor.dart'; +import 'dio_event_processor.dart'; import 'dio_stacktrace_extractor.dart'; import 'failed_request_interceptor.dart'; -import 'dio/lib/src/sentry_transformer.dart'; +import 'sentry_transformer.dart'; import 'sentry_dio_client_adapter.dart'; import 'version.dart'; From dae93064020e749b538974d773cec26eeab88bac Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:21:33 +0200 Subject: [PATCH 21/45] Update --- packages/dio/lib/sentry_dio.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dio/lib/sentry_dio.dart b/packages/dio/lib/sentry_dio.dart index f11674cf8a..d0238e4c10 100644 --- a/packages/dio/lib/sentry_dio.dart +++ b/packages/dio/lib/sentry_dio.dart @@ -1,7 +1,7 @@ library; -export '../../../dio/lib/src/sentry_dio_extension.dart'; +export 'src/sentry_dio_extension.dart'; // Export the processor in case people want to use it standalone. // Normally one doesn't need to use it directly. -export '../../../dio/lib/src/dio_event_processor.dart'; +export 'src/dio_event_processor.dart'; From 7cc04b4644e97a8c93587229677de577ca9f0885 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:22:49 +0200 Subject: [PATCH 22/45] Update --- packages/dio/pubspec_overrides.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/dio/pubspec_overrides.yaml b/packages/dio/pubspec_overrides.yaml index 0a17afda20..04ac16fe75 100644 --- a/packages/dio/pubspec_overrides.yaml +++ b/packages/dio/pubspec_overrides.yaml @@ -1,3 +1,4 @@ +# melos_managed_dependency_overrides: sentry dependency_overrides: sentry: - path: ../packages/dart + path: ../dart From 0706b8a30e1232b500e6dff99c70deb3cf73c643 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:26:46 +0200 Subject: [PATCH 23/45] Add hive to packages --- hive/CHANGELOG.md | 1 - hive/dartdoc_options.yaml | 1 - hive/pubspec_overrides.yaml | 3 --- {hive => packages/hive}/.gitignore | 0 packages/hive/CHANGELOG.md | 1 + {hive => packages/hive}/LICENSE | 0 {hive => packages/hive}/README.md | 0 {hive => packages/hive}/analysis_options.yaml | 0 {hive => packages/hive}/boxes_bad_keys.hive | 0 packages/hive/dartdoc_options.yaml | 1 + {hive => packages/hive}/example/example.dart | 0 {hive => packages/hive}/lib/sentry_hive.dart | 0 .../hive}/lib/src/default_compaction_strategy.dart | 0 {hive => packages/hive}/lib/src/default_key_comparator.dart | 0 {hive => packages/hive}/lib/src/sentry_box.dart | 0 {hive => packages/hive}/lib/src/sentry_box_base.dart | 0 {hive => packages/hive}/lib/src/sentry_box_collection.dart | 0 {hive => packages/hive}/lib/src/sentry_hive_impl.dart | 0 {hive => packages/hive}/lib/src/sentry_hive_interface.dart | 0 {hive => packages/hive}/lib/src/sentry_lazy_box.dart | 0 {hive => packages/hive}/lib/src/sentry_span_helper.dart | 0 {hive => packages/hive}/lib/src/version.dart | 0 {hive => packages/hive}/people-box-collection_bad_keys.hive | 0 {hive => packages/hive}/pubspec.yaml | 0 packages/hive/pubspec_overrides.yaml | 4 ++++ {hive => packages/hive}/test/mocks/mocks.dart | 0 {hive => packages/hive}/test/mocks/mocks.mocks.dart | 0 {hive => packages/hive}/test/person.dart | 0 {hive => packages/hive}/test/sentry_box_base_test.dart | 0 {hive => packages/hive}/test/sentry_box_collection_test.dart | 0 {hive => packages/hive}/test/sentry_hive_impl_test.dart | 0 {hive => packages/hive}/test/sentry_lazy_box_test.dart | 0 {hive => packages/hive}/test/utils.dart | 0 33 files changed, 6 insertions(+), 5 deletions(-) delete mode 120000 hive/CHANGELOG.md delete mode 120000 hive/dartdoc_options.yaml delete mode 100644 hive/pubspec_overrides.yaml rename {hive => packages/hive}/.gitignore (100%) create mode 120000 packages/hive/CHANGELOG.md rename {hive => packages/hive}/LICENSE (100%) rename {hive => packages/hive}/README.md (100%) rename {hive => packages/hive}/analysis_options.yaml (100%) rename {hive => packages/hive}/boxes_bad_keys.hive (100%) create mode 120000 packages/hive/dartdoc_options.yaml rename {hive => packages/hive}/example/example.dart (100%) rename {hive => packages/hive}/lib/sentry_hive.dart (100%) rename {hive => packages/hive}/lib/src/default_compaction_strategy.dart (100%) rename {hive => packages/hive}/lib/src/default_key_comparator.dart (100%) rename {hive => packages/hive}/lib/src/sentry_box.dart (100%) rename {hive => packages/hive}/lib/src/sentry_box_base.dart (100%) rename {hive => packages/hive}/lib/src/sentry_box_collection.dart (100%) rename {hive => packages/hive}/lib/src/sentry_hive_impl.dart (100%) rename {hive => packages/hive}/lib/src/sentry_hive_interface.dart (100%) rename {hive => packages/hive}/lib/src/sentry_lazy_box.dart (100%) rename {hive => packages/hive}/lib/src/sentry_span_helper.dart (100%) rename {hive => packages/hive}/lib/src/version.dart (100%) rename {hive => packages/hive}/people-box-collection_bad_keys.hive (100%) rename {hive => packages/hive}/pubspec.yaml (100%) create mode 100644 packages/hive/pubspec_overrides.yaml rename {hive => packages/hive}/test/mocks/mocks.dart (100%) rename {hive => packages/hive}/test/mocks/mocks.mocks.dart (100%) rename {hive => packages/hive}/test/person.dart (100%) rename {hive => packages/hive}/test/sentry_box_base_test.dart (100%) rename {hive => packages/hive}/test/sentry_box_collection_test.dart (100%) rename {hive => packages/hive}/test/sentry_hive_impl_test.dart (100%) rename {hive => packages/hive}/test/sentry_lazy_box_test.dart (100%) rename {hive => packages/hive}/test/utils.dart (100%) diff --git a/hive/CHANGELOG.md b/hive/CHANGELOG.md deleted file mode 120000 index 04c99a55ca..0000000000 --- a/hive/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../CHANGELOG.md \ No newline at end of file diff --git a/hive/dartdoc_options.yaml b/hive/dartdoc_options.yaml deleted file mode 120000 index fcb0f9cc26..0000000000 --- a/hive/dartdoc_options.yaml +++ /dev/null @@ -1 +0,0 @@ -../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/hive/pubspec_overrides.yaml b/hive/pubspec_overrides.yaml deleted file mode 100644 index 0a17afda20..0000000000 --- a/hive/pubspec_overrides.yaml +++ /dev/null @@ -1,3 +0,0 @@ -dependency_overrides: - sentry: - path: ../packages/dart diff --git a/hive/.gitignore b/packages/hive/.gitignore similarity index 100% rename from hive/.gitignore rename to packages/hive/.gitignore diff --git a/packages/hive/CHANGELOG.md b/packages/hive/CHANGELOG.md new file mode 120000 index 0000000000..699cc9e7b7 --- /dev/null +++ b/packages/hive/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/hive/LICENSE b/packages/hive/LICENSE similarity index 100% rename from hive/LICENSE rename to packages/hive/LICENSE diff --git a/hive/README.md b/packages/hive/README.md similarity index 100% rename from hive/README.md rename to packages/hive/README.md diff --git a/hive/analysis_options.yaml b/packages/hive/analysis_options.yaml similarity index 100% rename from hive/analysis_options.yaml rename to packages/hive/analysis_options.yaml diff --git a/hive/boxes_bad_keys.hive b/packages/hive/boxes_bad_keys.hive similarity index 100% rename from hive/boxes_bad_keys.hive rename to packages/hive/boxes_bad_keys.hive diff --git a/packages/hive/dartdoc_options.yaml b/packages/hive/dartdoc_options.yaml new file mode 120000 index 0000000000..7cbb8c0d74 --- /dev/null +++ b/packages/hive/dartdoc_options.yaml @@ -0,0 +1 @@ +../dart/dartdoc_options.yaml \ No newline at end of file diff --git a/hive/example/example.dart b/packages/hive/example/example.dart similarity index 100% rename from hive/example/example.dart rename to packages/hive/example/example.dart diff --git a/hive/lib/sentry_hive.dart b/packages/hive/lib/sentry_hive.dart similarity index 100% rename from hive/lib/sentry_hive.dart rename to packages/hive/lib/sentry_hive.dart diff --git a/hive/lib/src/default_compaction_strategy.dart b/packages/hive/lib/src/default_compaction_strategy.dart similarity index 100% rename from hive/lib/src/default_compaction_strategy.dart rename to packages/hive/lib/src/default_compaction_strategy.dart diff --git a/hive/lib/src/default_key_comparator.dart b/packages/hive/lib/src/default_key_comparator.dart similarity index 100% rename from hive/lib/src/default_key_comparator.dart rename to packages/hive/lib/src/default_key_comparator.dart diff --git a/hive/lib/src/sentry_box.dart b/packages/hive/lib/src/sentry_box.dart similarity index 100% rename from hive/lib/src/sentry_box.dart rename to packages/hive/lib/src/sentry_box.dart diff --git a/hive/lib/src/sentry_box_base.dart b/packages/hive/lib/src/sentry_box_base.dart similarity index 100% rename from hive/lib/src/sentry_box_base.dart rename to packages/hive/lib/src/sentry_box_base.dart diff --git a/hive/lib/src/sentry_box_collection.dart b/packages/hive/lib/src/sentry_box_collection.dart similarity index 100% rename from hive/lib/src/sentry_box_collection.dart rename to packages/hive/lib/src/sentry_box_collection.dart diff --git a/hive/lib/src/sentry_hive_impl.dart b/packages/hive/lib/src/sentry_hive_impl.dart similarity index 100% rename from hive/lib/src/sentry_hive_impl.dart rename to packages/hive/lib/src/sentry_hive_impl.dart diff --git a/hive/lib/src/sentry_hive_interface.dart b/packages/hive/lib/src/sentry_hive_interface.dart similarity index 100% rename from hive/lib/src/sentry_hive_interface.dart rename to packages/hive/lib/src/sentry_hive_interface.dart diff --git a/hive/lib/src/sentry_lazy_box.dart b/packages/hive/lib/src/sentry_lazy_box.dart similarity index 100% rename from hive/lib/src/sentry_lazy_box.dart rename to packages/hive/lib/src/sentry_lazy_box.dart diff --git a/hive/lib/src/sentry_span_helper.dart b/packages/hive/lib/src/sentry_span_helper.dart similarity index 100% rename from hive/lib/src/sentry_span_helper.dart rename to packages/hive/lib/src/sentry_span_helper.dart diff --git a/hive/lib/src/version.dart b/packages/hive/lib/src/version.dart similarity index 100% rename from hive/lib/src/version.dart rename to packages/hive/lib/src/version.dart diff --git a/hive/people-box-collection_bad_keys.hive b/packages/hive/people-box-collection_bad_keys.hive similarity index 100% rename from hive/people-box-collection_bad_keys.hive rename to packages/hive/people-box-collection_bad_keys.hive diff --git a/hive/pubspec.yaml b/packages/hive/pubspec.yaml similarity index 100% rename from hive/pubspec.yaml rename to packages/hive/pubspec.yaml diff --git a/packages/hive/pubspec_overrides.yaml b/packages/hive/pubspec_overrides.yaml new file mode 100644 index 0000000000..04ac16fe75 --- /dev/null +++ b/packages/hive/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# melos_managed_dependency_overrides: sentry +dependency_overrides: + sentry: + path: ../dart diff --git a/hive/test/mocks/mocks.dart b/packages/hive/test/mocks/mocks.dart similarity index 100% rename from hive/test/mocks/mocks.dart rename to packages/hive/test/mocks/mocks.dart diff --git a/hive/test/mocks/mocks.mocks.dart b/packages/hive/test/mocks/mocks.mocks.dart similarity index 100% rename from hive/test/mocks/mocks.mocks.dart rename to packages/hive/test/mocks/mocks.mocks.dart diff --git a/hive/test/person.dart b/packages/hive/test/person.dart similarity index 100% rename from hive/test/person.dart rename to packages/hive/test/person.dart diff --git a/hive/test/sentry_box_base_test.dart b/packages/hive/test/sentry_box_base_test.dart similarity index 100% rename from hive/test/sentry_box_base_test.dart rename to packages/hive/test/sentry_box_base_test.dart diff --git a/hive/test/sentry_box_collection_test.dart b/packages/hive/test/sentry_box_collection_test.dart similarity index 100% rename from hive/test/sentry_box_collection_test.dart rename to packages/hive/test/sentry_box_collection_test.dart diff --git a/hive/test/sentry_hive_impl_test.dart b/packages/hive/test/sentry_hive_impl_test.dart similarity index 100% rename from hive/test/sentry_hive_impl_test.dart rename to packages/hive/test/sentry_hive_impl_test.dart diff --git a/hive/test/sentry_lazy_box_test.dart b/packages/hive/test/sentry_lazy_box_test.dart similarity index 100% rename from hive/test/sentry_lazy_box_test.dart rename to packages/hive/test/sentry_lazy_box_test.dart diff --git a/hive/test/utils.dart b/packages/hive/test/utils.dart similarity index 100% rename from hive/test/utils.dart rename to packages/hive/test/utils.dart From 47ea449ece13170c209b7bae03fac7fe539b12e5 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:28:03 +0200 Subject: [PATCH 24/45] Update --- .github/workflows/hive.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/hive.yml b/.github/workflows/hive.yml index 9ca8828f82..c45b2bfb13 100644 --- a/.github/workflows/hive.yml +++ b/.github/workflows/hive.yml @@ -12,8 +12,8 @@ on: - '.github/workflows/analyze.yml' - '.github/actions/dart-test/**' - '.github/actions/coverage/**' - - 'dart/**' - - 'hive/**' + - 'packages/dart/**' + - 'packages/hive/**' jobs: cancel-previous-workflow: @@ -39,19 +39,19 @@ jobs: - uses: ./.github/actions/dart-test with: - directory: hive + directory: packages/hive web: false - uses: ./.github/actions/coverage if: runner.os == 'Linux' && matrix.sdk == 'stable' with: token: ${{ secrets.CODECOV_TOKEN }} - directory: hive + directory: packages/hive coverage: sentry_hive min-coverage: 55 analyze: uses: ./.github/workflows/analyze.yml with: - package: hive + package: packages/hive panaThreshold: 90 From 0679ecf1729782aa555a99484e3f928ad33c3696 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:33:40 +0200 Subject: [PATCH 25/45] Add file to packages --- .github/workflows/file.yml | 12 ++++++------ file/CHANGELOG.md | 1 - file/dartdoc_options.yaml | 1 - file/pubspec_overrides.yaml | 3 --- packages/file/CHANGELOG.md | 1 + {file => packages/file}/LICENSE | 0 {file => packages/file}/README.md | 0 {file => packages/file}/analysis_options.yaml | 0 {file => packages/file}/class-diagram.svg | 0 packages/file/dartdoc_options.yaml | 1 + {file => packages/file}/example/example.dart | 0 {file => packages/file}/lib/sentry_file.dart | 0 {file => packages/file}/lib/src/sentry_file.dart | 0 .../file}/lib/src/sentry_file_extension.dart | 0 .../file}/lib/src/sentry_io_overrides.dart | 0 .../lib/src/sentry_io_overrides_integration.dart | 0 {file => packages/file}/lib/src/version.dart | 0 {file => packages/file}/pubspec.yaml | 0 packages/file/pubspec_overrides.yaml | 4 ++++ .../file}/test/mock_sentry_client.dart | 0 .../file}/test/no_such_method_provider.dart | 0 .../file}/test/sentry_file_extension_test.dart | 0 {file => packages/file}/test/sentry_file_test.dart | 0 .../test/sentry_io_overrides_integration_test.dart | 0 {file => packages/file}/test/version_test.dart | 0 {file => packages/file}/test_resources/sentry.png | Bin {file => packages/file}/test_resources/testfile.txt | 0 27 files changed, 12 insertions(+), 11 deletions(-) delete mode 120000 file/CHANGELOG.md delete mode 120000 file/dartdoc_options.yaml delete mode 100644 file/pubspec_overrides.yaml create mode 120000 packages/file/CHANGELOG.md rename {file => packages/file}/LICENSE (100%) rename {file => packages/file}/README.md (100%) rename {file => packages/file}/analysis_options.yaml (100%) rename {file => packages/file}/class-diagram.svg (100%) create mode 120000 packages/file/dartdoc_options.yaml rename {file => packages/file}/example/example.dart (100%) rename {file => packages/file}/lib/sentry_file.dart (100%) rename {file => packages/file}/lib/src/sentry_file.dart (100%) rename {file => packages/file}/lib/src/sentry_file_extension.dart (100%) rename {file => packages/file}/lib/src/sentry_io_overrides.dart (100%) rename {file => packages/file}/lib/src/sentry_io_overrides_integration.dart (100%) rename {file => packages/file}/lib/src/version.dart (100%) rename {file => packages/file}/pubspec.yaml (100%) create mode 100644 packages/file/pubspec_overrides.yaml rename {file => packages/file}/test/mock_sentry_client.dart (100%) rename {file => packages/file}/test/no_such_method_provider.dart (100%) rename {file => packages/file}/test/sentry_file_extension_test.dart (100%) rename {file => packages/file}/test/sentry_file_test.dart (100%) rename {file => packages/file}/test/sentry_io_overrides_integration_test.dart (100%) rename {file => packages/file}/test/version_test.dart (100%) rename {file => packages/file}/test_resources/sentry.png (100%) rename {file => packages/file}/test_resources/testfile.txt (100%) diff --git a/.github/workflows/file.yml b/.github/workflows/file.yml index eda4c26ae3..fee9c88c07 100644 --- a/.github/workflows/file.yml +++ b/.github/workflows/file.yml @@ -12,9 +12,9 @@ on: - '.github/workflows/analyze.yml' - '.github/actions/dart-test/**' - '.github/actions/coverage/**' - - 'dart/**' - - 'flutter/**' - - 'file/**' + - 'packages/dart/**' + - 'packages/flutter/**' + - 'packages/file/**' jobs: cancel-previous-workflow: @@ -40,19 +40,19 @@ jobs: - uses: ./.github/actions/dart-test with: - directory: file + directory: packages/file web: false - uses: ./.github/actions/coverage if: runner.os == 'Linux' && matrix.sdk == 'stable' with: token: ${{ secrets.CODECOV_TOKEN }} - directory: file + directory: packages/file coverage: sentry_file min-coverage: 55 analyze: uses: ./.github/workflows/analyze.yml with: - package: file + package: packages/file panaThreshold: 90 diff --git a/file/CHANGELOG.md b/file/CHANGELOG.md deleted file mode 120000 index 04c99a55ca..0000000000 --- a/file/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../CHANGELOG.md \ No newline at end of file diff --git a/file/dartdoc_options.yaml b/file/dartdoc_options.yaml deleted file mode 120000 index fcb0f9cc26..0000000000 --- a/file/dartdoc_options.yaml +++ /dev/null @@ -1 +0,0 @@ -../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/file/pubspec_overrides.yaml b/file/pubspec_overrides.yaml deleted file mode 100644 index 0a17afda20..0000000000 --- a/file/pubspec_overrides.yaml +++ /dev/null @@ -1,3 +0,0 @@ -dependency_overrides: - sentry: - path: ../packages/dart diff --git a/packages/file/CHANGELOG.md b/packages/file/CHANGELOG.md new file mode 120000 index 0000000000..699cc9e7b7 --- /dev/null +++ b/packages/file/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/file/LICENSE b/packages/file/LICENSE similarity index 100% rename from file/LICENSE rename to packages/file/LICENSE diff --git a/file/README.md b/packages/file/README.md similarity index 100% rename from file/README.md rename to packages/file/README.md diff --git a/file/analysis_options.yaml b/packages/file/analysis_options.yaml similarity index 100% rename from file/analysis_options.yaml rename to packages/file/analysis_options.yaml diff --git a/file/class-diagram.svg b/packages/file/class-diagram.svg similarity index 100% rename from file/class-diagram.svg rename to packages/file/class-diagram.svg diff --git a/packages/file/dartdoc_options.yaml b/packages/file/dartdoc_options.yaml new file mode 120000 index 0000000000..7cbb8c0d74 --- /dev/null +++ b/packages/file/dartdoc_options.yaml @@ -0,0 +1 @@ +../dart/dartdoc_options.yaml \ No newline at end of file diff --git a/file/example/example.dart b/packages/file/example/example.dart similarity index 100% rename from file/example/example.dart rename to packages/file/example/example.dart diff --git a/file/lib/sentry_file.dart b/packages/file/lib/sentry_file.dart similarity index 100% rename from file/lib/sentry_file.dart rename to packages/file/lib/sentry_file.dart diff --git a/file/lib/src/sentry_file.dart b/packages/file/lib/src/sentry_file.dart similarity index 100% rename from file/lib/src/sentry_file.dart rename to packages/file/lib/src/sentry_file.dart diff --git a/file/lib/src/sentry_file_extension.dart b/packages/file/lib/src/sentry_file_extension.dart similarity index 100% rename from file/lib/src/sentry_file_extension.dart rename to packages/file/lib/src/sentry_file_extension.dart diff --git a/file/lib/src/sentry_io_overrides.dart b/packages/file/lib/src/sentry_io_overrides.dart similarity index 100% rename from file/lib/src/sentry_io_overrides.dart rename to packages/file/lib/src/sentry_io_overrides.dart diff --git a/file/lib/src/sentry_io_overrides_integration.dart b/packages/file/lib/src/sentry_io_overrides_integration.dart similarity index 100% rename from file/lib/src/sentry_io_overrides_integration.dart rename to packages/file/lib/src/sentry_io_overrides_integration.dart diff --git a/file/lib/src/version.dart b/packages/file/lib/src/version.dart similarity index 100% rename from file/lib/src/version.dart rename to packages/file/lib/src/version.dart diff --git a/file/pubspec.yaml b/packages/file/pubspec.yaml similarity index 100% rename from file/pubspec.yaml rename to packages/file/pubspec.yaml diff --git a/packages/file/pubspec_overrides.yaml b/packages/file/pubspec_overrides.yaml new file mode 100644 index 0000000000..04ac16fe75 --- /dev/null +++ b/packages/file/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# melos_managed_dependency_overrides: sentry +dependency_overrides: + sentry: + path: ../dart diff --git a/file/test/mock_sentry_client.dart b/packages/file/test/mock_sentry_client.dart similarity index 100% rename from file/test/mock_sentry_client.dart rename to packages/file/test/mock_sentry_client.dart diff --git a/file/test/no_such_method_provider.dart b/packages/file/test/no_such_method_provider.dart similarity index 100% rename from file/test/no_such_method_provider.dart rename to packages/file/test/no_such_method_provider.dart diff --git a/file/test/sentry_file_extension_test.dart b/packages/file/test/sentry_file_extension_test.dart similarity index 100% rename from file/test/sentry_file_extension_test.dart rename to packages/file/test/sentry_file_extension_test.dart diff --git a/file/test/sentry_file_test.dart b/packages/file/test/sentry_file_test.dart similarity index 100% rename from file/test/sentry_file_test.dart rename to packages/file/test/sentry_file_test.dart diff --git a/file/test/sentry_io_overrides_integration_test.dart b/packages/file/test/sentry_io_overrides_integration_test.dart similarity index 100% rename from file/test/sentry_io_overrides_integration_test.dart rename to packages/file/test/sentry_io_overrides_integration_test.dart diff --git a/file/test/version_test.dart b/packages/file/test/version_test.dart similarity index 100% rename from file/test/version_test.dart rename to packages/file/test/version_test.dart diff --git a/file/test_resources/sentry.png b/packages/file/test_resources/sentry.png similarity index 100% rename from file/test_resources/sentry.png rename to packages/file/test_resources/sentry.png diff --git a/file/test_resources/testfile.txt b/packages/file/test_resources/testfile.txt similarity index 100% rename from file/test_resources/testfile.txt rename to packages/file/test_resources/testfile.txt From 784ef2d52d8c093451178dac14f13fde8b160cd4 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:37:02 +0200 Subject: [PATCH 26/45] Add link to packages --- .github/workflows/link.yml | 8 ++++---- link/CHANGELOG.md | 1 - {link => packages/link}/.gitignore | 0 packages/link/CHANGELOG.md | 1 + {link => packages/link}/CHANGELOG_OLD.md | 0 {link => packages/link}/LICENSE | 0 {link => packages/link}/README.md | 0 {link => packages/link}/analysis_options.yaml | 0 packages/link/dartdoc_options.yaml | 1 + {link => packages/link}/example/example.dart | 0 {link => packages/link}/lib/sentry_link.dart | 0 {link => packages/link}/lib/src/extension.dart | 0 {link => packages/link}/lib/src/extractors.dart | 0 .../link}/lib/src/graph_gl_filter.dart | 0 .../link}/lib/src/sentry_breadcrumb_link.dart | 0 {link => packages/link}/lib/src/sentry_gql.dart | 0 {link => packages/link}/lib/src/sentry_link.dart | 0 .../link}/lib/src/sentry_request_serializer.dart | 0 .../link}/lib/src/sentry_response_parser.dart | 0 .../link}/lib/src/sentry_tracing_link.dart | 0 {link => packages/link}/pubspec.yaml | 0 {link => packages/link}/pubspec_overrides.yaml | 0 {link => packages/link}/screenshot.png | Bin .../link}/test/graph_gl_filter_test.dart | 0 .../link}/test/link_exception_extractor_test.dart | 0 25 files changed, 6 insertions(+), 5 deletions(-) delete mode 120000 link/CHANGELOG.md rename {link => packages/link}/.gitignore (100%) create mode 120000 packages/link/CHANGELOG.md rename {link => packages/link}/CHANGELOG_OLD.md (100%) rename {link => packages/link}/LICENSE (100%) rename {link => packages/link}/README.md (100%) rename {link => packages/link}/analysis_options.yaml (100%) create mode 120000 packages/link/dartdoc_options.yaml rename {link => packages/link}/example/example.dart (100%) rename {link => packages/link}/lib/sentry_link.dart (100%) rename {link => packages/link}/lib/src/extension.dart (100%) rename {link => packages/link}/lib/src/extractors.dart (100%) rename {link => packages/link}/lib/src/graph_gl_filter.dart (100%) rename {link => packages/link}/lib/src/sentry_breadcrumb_link.dart (100%) rename {link => packages/link}/lib/src/sentry_gql.dart (100%) rename {link => packages/link}/lib/src/sentry_link.dart (100%) rename {link => packages/link}/lib/src/sentry_request_serializer.dart (100%) rename {link => packages/link}/lib/src/sentry_response_parser.dart (100%) rename {link => packages/link}/lib/src/sentry_tracing_link.dart (100%) rename {link => packages/link}/pubspec.yaml (100%) rename {link => packages/link}/pubspec_overrides.yaml (100%) rename {link => packages/link}/screenshot.png (100%) rename {link => packages/link}/test/graph_gl_filter_test.dart (100%) rename {link => packages/link}/test/link_exception_extractor_test.dart (100%) diff --git a/.github/workflows/link.yml b/.github/workflows/link.yml index 190ae0100b..8d7075fa78 100644 --- a/.github/workflows/link.yml +++ b/.github/workflows/link.yml @@ -12,8 +12,8 @@ on: - '.github/workflows/analyze.yml' - '.github/actions/dart-test/**' - '.github/actions/coverage/**' - - 'dart/**' - - 'link/**' + - 'packages/dart/**' + - 'packages/link/**' jobs: cancel-previous-workflow: @@ -39,7 +39,7 @@ jobs: - uses: ./.github/actions/dart-test with: - directory: link + directory: packages/link web: false # TODO: don't set coverage for now to finish publishing it @@ -54,4 +54,4 @@ jobs: analyze: uses: ./.github/workflows/analyze.yml with: - package: link + package: packages/link diff --git a/link/CHANGELOG.md b/link/CHANGELOG.md deleted file mode 120000 index 04c99a55ca..0000000000 --- a/link/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../CHANGELOG.md \ No newline at end of file diff --git a/link/.gitignore b/packages/link/.gitignore similarity index 100% rename from link/.gitignore rename to packages/link/.gitignore diff --git a/packages/link/CHANGELOG.md b/packages/link/CHANGELOG.md new file mode 120000 index 0000000000..699cc9e7b7 --- /dev/null +++ b/packages/link/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/link/CHANGELOG_OLD.md b/packages/link/CHANGELOG_OLD.md similarity index 100% rename from link/CHANGELOG_OLD.md rename to packages/link/CHANGELOG_OLD.md diff --git a/link/LICENSE b/packages/link/LICENSE similarity index 100% rename from link/LICENSE rename to packages/link/LICENSE diff --git a/link/README.md b/packages/link/README.md similarity index 100% rename from link/README.md rename to packages/link/README.md diff --git a/link/analysis_options.yaml b/packages/link/analysis_options.yaml similarity index 100% rename from link/analysis_options.yaml rename to packages/link/analysis_options.yaml diff --git a/packages/link/dartdoc_options.yaml b/packages/link/dartdoc_options.yaml new file mode 120000 index 0000000000..7cbb8c0d74 --- /dev/null +++ b/packages/link/dartdoc_options.yaml @@ -0,0 +1 @@ +../dart/dartdoc_options.yaml \ No newline at end of file diff --git a/link/example/example.dart b/packages/link/example/example.dart similarity index 100% rename from link/example/example.dart rename to packages/link/example/example.dart diff --git a/link/lib/sentry_link.dart b/packages/link/lib/sentry_link.dart similarity index 100% rename from link/lib/sentry_link.dart rename to packages/link/lib/sentry_link.dart diff --git a/link/lib/src/extension.dart b/packages/link/lib/src/extension.dart similarity index 100% rename from link/lib/src/extension.dart rename to packages/link/lib/src/extension.dart diff --git a/link/lib/src/extractors.dart b/packages/link/lib/src/extractors.dart similarity index 100% rename from link/lib/src/extractors.dart rename to packages/link/lib/src/extractors.dart diff --git a/link/lib/src/graph_gl_filter.dart b/packages/link/lib/src/graph_gl_filter.dart similarity index 100% rename from link/lib/src/graph_gl_filter.dart rename to packages/link/lib/src/graph_gl_filter.dart diff --git a/link/lib/src/sentry_breadcrumb_link.dart b/packages/link/lib/src/sentry_breadcrumb_link.dart similarity index 100% rename from link/lib/src/sentry_breadcrumb_link.dart rename to packages/link/lib/src/sentry_breadcrumb_link.dart diff --git a/link/lib/src/sentry_gql.dart b/packages/link/lib/src/sentry_gql.dart similarity index 100% rename from link/lib/src/sentry_gql.dart rename to packages/link/lib/src/sentry_gql.dart diff --git a/link/lib/src/sentry_link.dart b/packages/link/lib/src/sentry_link.dart similarity index 100% rename from link/lib/src/sentry_link.dart rename to packages/link/lib/src/sentry_link.dart diff --git a/link/lib/src/sentry_request_serializer.dart b/packages/link/lib/src/sentry_request_serializer.dart similarity index 100% rename from link/lib/src/sentry_request_serializer.dart rename to packages/link/lib/src/sentry_request_serializer.dart diff --git a/link/lib/src/sentry_response_parser.dart b/packages/link/lib/src/sentry_response_parser.dart similarity index 100% rename from link/lib/src/sentry_response_parser.dart rename to packages/link/lib/src/sentry_response_parser.dart diff --git a/link/lib/src/sentry_tracing_link.dart b/packages/link/lib/src/sentry_tracing_link.dart similarity index 100% rename from link/lib/src/sentry_tracing_link.dart rename to packages/link/lib/src/sentry_tracing_link.dart diff --git a/link/pubspec.yaml b/packages/link/pubspec.yaml similarity index 100% rename from link/pubspec.yaml rename to packages/link/pubspec.yaml diff --git a/link/pubspec_overrides.yaml b/packages/link/pubspec_overrides.yaml similarity index 100% rename from link/pubspec_overrides.yaml rename to packages/link/pubspec_overrides.yaml diff --git a/link/screenshot.png b/packages/link/screenshot.png similarity index 100% rename from link/screenshot.png rename to packages/link/screenshot.png diff --git a/link/test/graph_gl_filter_test.dart b/packages/link/test/graph_gl_filter_test.dart similarity index 100% rename from link/test/graph_gl_filter_test.dart rename to packages/link/test/graph_gl_filter_test.dart diff --git a/link/test/link_exception_extractor_test.dart b/packages/link/test/link_exception_extractor_test.dart similarity index 100% rename from link/test/link_exception_extractor_test.dart rename to packages/link/test/link_exception_extractor_test.dart From f5a74a7a6fe6c8b0a99e569892be3c5f84eb0e97 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:38:50 +0200 Subject: [PATCH 27/45] Update --- packages/link/pubspec_overrides.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/link/pubspec_overrides.yaml b/packages/link/pubspec_overrides.yaml index 0a17afda20..04ac16fe75 100644 --- a/packages/link/pubspec_overrides.yaml +++ b/packages/link/pubspec_overrides.yaml @@ -1,3 +1,4 @@ +# melos_managed_dependency_overrides: sentry dependency_overrides: sentry: - path: ../packages/dart + path: ../dart From 326b8097b49db2bd6aef474ed1075d4c60ad9c3c Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:52:43 +0200 Subject: [PATCH 28/45] Update --- .github/workflows/drift.yml | 32 ++++++++++++------ drift/CHANGELOG.md | 1 - drift/dartdoc_options.yaml | 1 - drift/pubspec_overrides.yaml | 3 -- {drift => packages/drift}/.gitignore | 0 packages/drift/CHANGELOG.md | 1 + {drift => packages/drift}/LICENSE | 0 {drift => packages/drift}/README.md | 0 .../drift}/analysis_options.yaml | 0 packages/drift/dartdoc_options.yaml | 1 + .../drift}/example/database.dart | 0 .../drift}/example/database.g.dart | 0 .../drift}/example/example.dart | 0 .../drift}/lib/sentry_drift.dart | 0 .../drift}/lib/src/constants.dart | 0 .../lib/src/sentry_query_interceptor.dart | 0 .../drift}/lib/src/sentry_span_helper.dart | 0 .../drift}/lib/src/version.dart | 0 {drift => packages/drift}/pubspec.yaml | 4 +-- packages/drift/pubspec_overrides.yaml | 4 +++ .../drift}/test/mocks/mocks.dart | 0 .../drift}/test/mocks/mocks.mocks.dart | 0 .../drift}/test/sentry_drift_test.dart | 2 +- {drift => packages/drift}/test/sqlite3.dll | Bin .../drift}/test/test_database.dart | 0 .../drift}/test/test_database.g.dart | 0 {drift => packages/drift}/test/utils.dart | 0 .../drift}/test/utils/windows_helper.dart | 0 28 files changed, 30 insertions(+), 19 deletions(-) delete mode 120000 drift/CHANGELOG.md delete mode 120000 drift/dartdoc_options.yaml delete mode 100644 drift/pubspec_overrides.yaml rename {drift => packages/drift}/.gitignore (100%) create mode 120000 packages/drift/CHANGELOG.md rename {drift => packages/drift}/LICENSE (100%) rename {drift => packages/drift}/README.md (100%) rename {drift => packages/drift}/analysis_options.yaml (100%) create mode 120000 packages/drift/dartdoc_options.yaml rename {drift => packages/drift}/example/database.dart (100%) rename {drift => packages/drift}/example/database.g.dart (100%) rename {drift => packages/drift}/example/example.dart (100%) rename {drift => packages/drift}/lib/sentry_drift.dart (100%) rename {drift => packages/drift}/lib/src/constants.dart (100%) rename {drift => packages/drift}/lib/src/sentry_query_interceptor.dart (100%) rename {drift => packages/drift}/lib/src/sentry_span_helper.dart (100%) rename {drift => packages/drift}/lib/src/version.dart (100%) rename {drift => packages/drift}/pubspec.yaml (91%) create mode 100644 packages/drift/pubspec_overrides.yaml rename {drift => packages/drift}/test/mocks/mocks.dart (100%) rename {drift => packages/drift}/test/mocks/mocks.mocks.dart (100%) rename {drift => packages/drift}/test/sentry_drift_test.dart (99%) rename {drift => packages/drift}/test/sqlite3.dll (100%) rename {drift => packages/drift}/test/test_database.dart (100%) rename {drift => packages/drift}/test/test_database.g.dart (100%) rename {drift => packages/drift}/test/utils.dart (100%) rename {drift => packages/drift}/test/utils/windows_helper.dart (100%) diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml index 43d6f9e435..160a4ee424 100644 --- a/.github/workflows/drift.yml +++ b/.github/workflows/drift.yml @@ -10,10 +10,10 @@ on: - '!**/class-diagram.svg' - '.github/workflows/drift.yml' - '.github/workflows/analyze.yml' - - '.github/actions/flutter-test/**' + - '.github/actions/dart-test/**' - '.github/actions/coverage/**' - - 'dart/**' - - 'drift/**' + - 'packages/dart/**' + - 'packages/drift/**' jobs: cancel-previous-workflow: @@ -37,24 +37,36 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install libsqlite3 - if: matrix.target == 'linux' + - name: Install libsqlite3 (Linux) + if: matrix.os == 'ubuntu' run: sudo apt-get -y install libsqlite3-dev - - uses: ./.github/actions/flutter-test + - name: Install SQLite (macOS) + if: matrix.os == 'macos' + run: brew install sqlite3 + + - name: Install SQLite (Windows) + if: matrix.os == 'windows' + shell: powershell + run: | + Invoke-WebRequest -Uri "https://www.sqlite.org/2023/sqlite-dll-win64-x64-3420000.zip" -OutFile "sqlite.zip" + Expand-Archive -Path "sqlite.zip" -DestinationPath "C:\sqlite" + echo "C:\sqlite" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - uses: ./.github/actions/dart-test with: - directory: drift + directory: packages/drift - uses: ./.github/actions/coverage if: matrix.target == 'linux' && matrix.sdk == 'stable' with: token: ${{ secrets.CODECOV_TOKEN }} - directory: drift + directory: packages/drift coverage: sentry_drift min-coverage: 80 analyze: uses: ./.github/workflows/analyze.yml with: - package: drift - sdk: flutter + package: packages/drift + sdk: dart diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md deleted file mode 120000 index 04c99a55ca..0000000000 --- a/drift/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../CHANGELOG.md \ No newline at end of file diff --git a/drift/dartdoc_options.yaml b/drift/dartdoc_options.yaml deleted file mode 120000 index fcb0f9cc26..0000000000 --- a/drift/dartdoc_options.yaml +++ /dev/null @@ -1 +0,0 @@ -../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/drift/pubspec_overrides.yaml b/drift/pubspec_overrides.yaml deleted file mode 100644 index 0a17afda20..0000000000 --- a/drift/pubspec_overrides.yaml +++ /dev/null @@ -1,3 +0,0 @@ -dependency_overrides: - sentry: - path: ../packages/dart diff --git a/drift/.gitignore b/packages/drift/.gitignore similarity index 100% rename from drift/.gitignore rename to packages/drift/.gitignore diff --git a/packages/drift/CHANGELOG.md b/packages/drift/CHANGELOG.md new file mode 120000 index 0000000000..699cc9e7b7 --- /dev/null +++ b/packages/drift/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/drift/LICENSE b/packages/drift/LICENSE similarity index 100% rename from drift/LICENSE rename to packages/drift/LICENSE diff --git a/drift/README.md b/packages/drift/README.md similarity index 100% rename from drift/README.md rename to packages/drift/README.md diff --git a/drift/analysis_options.yaml b/packages/drift/analysis_options.yaml similarity index 100% rename from drift/analysis_options.yaml rename to packages/drift/analysis_options.yaml diff --git a/packages/drift/dartdoc_options.yaml b/packages/drift/dartdoc_options.yaml new file mode 120000 index 0000000000..7cbb8c0d74 --- /dev/null +++ b/packages/drift/dartdoc_options.yaml @@ -0,0 +1 @@ +../dart/dartdoc_options.yaml \ No newline at end of file diff --git a/drift/example/database.dart b/packages/drift/example/database.dart similarity index 100% rename from drift/example/database.dart rename to packages/drift/example/database.dart diff --git a/drift/example/database.g.dart b/packages/drift/example/database.g.dart similarity index 100% rename from drift/example/database.g.dart rename to packages/drift/example/database.g.dart diff --git a/drift/example/example.dart b/packages/drift/example/example.dart similarity index 100% rename from drift/example/example.dart rename to packages/drift/example/example.dart diff --git a/drift/lib/sentry_drift.dart b/packages/drift/lib/sentry_drift.dart similarity index 100% rename from drift/lib/sentry_drift.dart rename to packages/drift/lib/sentry_drift.dart diff --git a/drift/lib/src/constants.dart b/packages/drift/lib/src/constants.dart similarity index 100% rename from drift/lib/src/constants.dart rename to packages/drift/lib/src/constants.dart diff --git a/drift/lib/src/sentry_query_interceptor.dart b/packages/drift/lib/src/sentry_query_interceptor.dart similarity index 100% rename from drift/lib/src/sentry_query_interceptor.dart rename to packages/drift/lib/src/sentry_query_interceptor.dart diff --git a/drift/lib/src/sentry_span_helper.dart b/packages/drift/lib/src/sentry_span_helper.dart similarity index 100% rename from drift/lib/src/sentry_span_helper.dart rename to packages/drift/lib/src/sentry_span_helper.dart diff --git a/drift/lib/src/version.dart b/packages/drift/lib/src/version.dart similarity index 100% rename from drift/lib/src/version.dart rename to packages/drift/lib/src/version.dart diff --git a/drift/pubspec.yaml b/packages/drift/pubspec.yaml similarity index 91% rename from drift/pubspec.yaml rename to packages/drift/pubspec.yaml index 35d76e6b6a..5820fc0b7b 100644 --- a/drift/pubspec.yaml +++ b/packages/drift/pubspec.yaml @@ -23,13 +23,11 @@ dependencies: dev_dependencies: lints: '>=2.0.0' - flutter_test: - sdk: flutter + test: ^1.26.3 coverage: ^1.3.0 mockito: ^5.1.0 build_runner: ^2.4.6 drift_dev: ^2.13.0 yaml: ^3.1.0 # needed for version match (code and pubspec) - sqlite3_flutter_libs: ^0.5.0 sqlite3: ^2.1.0 archive: ^3.1.2 diff --git a/packages/drift/pubspec_overrides.yaml b/packages/drift/pubspec_overrides.yaml new file mode 100644 index 0000000000..04ac16fe75 --- /dev/null +++ b/packages/drift/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# melos_managed_dependency_overrides: sentry +dependency_overrides: + sentry: + path: ../dart diff --git a/drift/test/mocks/mocks.dart b/packages/drift/test/mocks/mocks.dart similarity index 100% rename from drift/test/mocks/mocks.dart rename to packages/drift/test/mocks/mocks.dart diff --git a/drift/test/mocks/mocks.mocks.dart b/packages/drift/test/mocks/mocks.mocks.dart similarity index 100% rename from drift/test/mocks/mocks.mocks.dart rename to packages/drift/test/mocks/mocks.mocks.dart diff --git a/drift/test/sentry_drift_test.dart b/packages/drift/test/sentry_drift_test.dart similarity index 99% rename from drift/test/sentry_drift_test.dart rename to packages/drift/test/sentry_drift_test.dart index ab115e6b2a..27ce6b8320 100644 --- a/drift/test/sentry_drift_test.dart +++ b/packages/drift/test/sentry_drift_test.dart @@ -4,7 +4,7 @@ import 'package:drift/drift.dart'; import 'package:drift/native.dart'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:mockito/mockito.dart'; import 'package:sentry/sentry.dart'; import 'package:sentry/src/sentry_tracer.dart'; diff --git a/drift/test/sqlite3.dll b/packages/drift/test/sqlite3.dll similarity index 100% rename from drift/test/sqlite3.dll rename to packages/drift/test/sqlite3.dll diff --git a/drift/test/test_database.dart b/packages/drift/test/test_database.dart similarity index 100% rename from drift/test/test_database.dart rename to packages/drift/test/test_database.dart diff --git a/drift/test/test_database.g.dart b/packages/drift/test/test_database.g.dart similarity index 100% rename from drift/test/test_database.g.dart rename to packages/drift/test/test_database.g.dart diff --git a/drift/test/utils.dart b/packages/drift/test/utils.dart similarity index 100% rename from drift/test/utils.dart rename to packages/drift/test/utils.dart diff --git a/drift/test/utils/windows_helper.dart b/packages/drift/test/utils/windows_helper.dart similarity index 100% rename from drift/test/utils/windows_helper.dart rename to packages/drift/test/utils/windows_helper.dart From 1364687ef10870ccd2c5ebd16e1e1acf59b21018 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:55:11 +0200 Subject: [PATCH 29/45] Update --- .github/workflows/drift.yml | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml index 160a4ee424..bd34f7b513 100644 --- a/.github/workflows/drift.yml +++ b/.github/workflows/drift.yml @@ -37,22 +37,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install libsqlite3 (Linux) - if: matrix.os == 'ubuntu' + - name: Install libsqlite3 + if: matrix.target == 'linux' run: sudo apt-get -y install libsqlite3-dev - - name: Install SQLite (macOS) - if: matrix.os == 'macos' - run: brew install sqlite3 - - - name: Install SQLite (Windows) - if: matrix.os == 'windows' - shell: powershell - run: | - Invoke-WebRequest -Uri "https://www.sqlite.org/2023/sqlite-dll-win64-x64-3420000.zip" -OutFile "sqlite.zip" - Expand-Archive -Path "sqlite.zip" -DestinationPath "C:\sqlite" - echo "C:\sqlite" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - uses: ./.github/actions/dart-test with: directory: packages/drift From b7af7527d245f999c9d6a6c5c88dd2e7880a0f00 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 12:57:33 +0200 Subject: [PATCH 30/45] Update --- .github/workflows/drift.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml index bd34f7b513..f51dd4464e 100644 --- a/.github/workflows/drift.yml +++ b/.github/workflows/drift.yml @@ -57,4 +57,3 @@ jobs: uses: ./.github/workflows/analyze.yml with: package: packages/drift - sdk: dart From e3e05e53534ae3789eb6eb1c99e07cc082eabfd2 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 13:03:04 +0200 Subject: [PATCH 31/45] Fix test --- .github/workflows/drift.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml index f51dd4464e..3b52ccfc79 100644 --- a/.github/workflows/drift.yml +++ b/.github/workflows/drift.yml @@ -44,6 +44,7 @@ jobs: - uses: ./.github/actions/dart-test with: directory: packages/drift + web: 'false' - uses: ./.github/actions/coverage if: matrix.target == 'linux' && matrix.sdk == 'stable' From ea6336af2c1eefa06c51ae9ecd8b6814d73b9c9d Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 13:09:22 +0200 Subject: [PATCH 32/45] Fix test --- .github/workflows/drift.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml index 3b52ccfc79..aeb45d159d 100644 --- a/.github/workflows/drift.yml +++ b/.github/workflows/drift.yml @@ -41,6 +41,12 @@ jobs: if: matrix.target == 'linux' run: sudo apt-get -y install libsqlite3-dev + - name: Install SQLite on Windows + if: matrix.target == 'windows' + run: | + choco install sqlite -y + echo "C:/ProgramData/chocolatey/lib/sqlite/tools" >> $GITHUB_PATH + - uses: ./.github/actions/dart-test with: directory: packages/drift From ccbc1584ef81612d2ecdd1e3f195ca11d7f24c00 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 13:16:08 +0200 Subject: [PATCH 33/45] Fix test --- .github/workflows/drift.yml | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml index aeb45d159d..3429783304 100644 --- a/.github/workflows/drift.yml +++ b/.github/workflows/drift.yml @@ -25,27 +25,33 @@ jobs: access_token: ${{ github.token }} build: - name: '${{ matrix.target }} | ${{ matrix.sdk }}' - runs-on: ${{ matrix.target == 'linux' && 'ubuntu' || matrix.target }}-latest + name: '${{ matrix.os }} | ${{ matrix.sdk }}' + runs-on: ${{ matrix.os }}-latest timeout-minutes: 30 strategy: fail-fast: false matrix: - target: [macos, linux, windows] + os: [ ubuntu, macos, windows ] sdk: [stable, beta] steps: - uses: actions/checkout@v4 - - name: Install libsqlite3 - if: matrix.target == 'linux' + - name: Install libsqlite3 (Linux) + if: matrix.os == 'ubuntu' run: sudo apt-get -y install libsqlite3-dev - - name: Install SQLite on Windows - if: matrix.target == 'windows' + - name: Install SQLite (macOS) + if: matrix.os == 'macos' + run: brew install sqlite3 + + - name: Install SQLite (Windows) + if: matrix.os == 'windows' + shell: powershell run: | - choco install sqlite -y - echo "C:/ProgramData/chocolatey/lib/sqlite/tools" >> $GITHUB_PATH + Invoke-WebRequest -Uri "https://www.sqlite.org/2023/sqlite-dll-win64-x64-3420000.zip" -OutFile "sqlite.zip" + Expand-Archive -Path "sqlite.zip" -DestinationPath "C:\sqlite" + echo "C:\sqlite" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - uses: ./.github/actions/dart-test with: @@ -53,7 +59,7 @@ jobs: web: 'false' - uses: ./.github/actions/coverage - if: matrix.target == 'linux' && matrix.sdk == 'stable' + if: matrix.os == 'ubuntu' && matrix.sdk == 'stable' with: token: ${{ secrets.CODECOV_TOKEN }} directory: packages/drift From 049e3dc5d916b0b71b62dee017b1dc1f5d3bbc71 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 13:21:05 +0200 Subject: [PATCH 34/45] Fix test --- .github/workflows/drift.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml index 3429783304..4a4906dc6b 100644 --- a/.github/workflows/drift.yml +++ b/.github/workflows/drift.yml @@ -49,9 +49,8 @@ jobs: if: matrix.os == 'windows' shell: powershell run: | - Invoke-WebRequest -Uri "https://www.sqlite.org/2023/sqlite-dll-win64-x64-3420000.zip" -OutFile "sqlite.zip" - Expand-Archive -Path "sqlite.zip" -DestinationPath "C:\sqlite" - echo "C:\sqlite" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + choco install sqlite -y + echo "C:/ProgramData/chocolatey/lib/sqlite/tools" >> $GITHUB_PATH - uses: ./.github/actions/dart-test with: From 476b8ed85306b6e93facc15d82a99e82c2283b1e Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 13:41:16 +0200 Subject: [PATCH 35/45] Fix test --- packages/drift/test/utils/windows_helper.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/drift/test/utils/windows_helper.dart b/packages/drift/test/utils/windows_helper.dart index e77fb03bed..984ee55504 100644 --- a/packages/drift/test/utils/windows_helper.dart +++ b/packages/drift/test/utils/windows_helper.dart @@ -1,8 +1,14 @@ import 'dart:ffi'; -import 'dart:io'; DynamicLibrary openOnWindows() { - final scriptDir = File(Platform.script.toFilePath()).parent; - final libraryNextToScript = File('${scriptDir.path}/test/sqlite3.dll'); - return DynamicLibrary.open(libraryNextToScript.path); + // Use the system SQLite DLL installed by Chocolatey + try { + // Try to load from system path first + return DynamicLibrary.open('sqlite3.dll'); + } catch (e) { + // Fallback to absolute path from Chocolatey installation + return DynamicLibrary.open( + 'C:\\ProgramData\\chocolatey\\lib\\sqlite\\tools\\sqlite3.dll', + ); + } } From 2f0529d2b4a9b8bce49cf897a7ffa8772d68cd7c Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 13:52:01 +0200 Subject: [PATCH 36/45] Add flutter to packages --- .../contents.xcworkspacedata | 7 - {flutter => packages/flutter}/.editorconfig | 0 {flutter => packages/flutter}/.gitignore | 0 {flutter => packages/flutter}/.metadata | 0 {flutter => packages/flutter}/.pubignore | 0 {flutter => packages/flutter}/CHANGELOG.md | 0 {flutter => packages/flutter}/LICENSE | 0 {flutter => packages/flutter}/README.md | 0 .../flutter}/analysis_options.yaml | 0 .../flutter}/android/.gitignore | 0 .../flutter}/android/build.gradle | 0 .../flutter}/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../flutter}/android/proguard-rules.pro | 0 .../flutter}/android/settings.gradle | 0 .../android/src/main/AndroidManifest.xml | 0 .../kotlin/io/sentry/flutter/SentryFlutter.kt | 0 .../io/sentry/flutter/SentryFlutterPlugin.kt | 0 .../SentryFlutterReplayBreadcrumbConverter.kt | 0 .../flutter/SentryFlutterReplayRecorder.kt | 0 .../io/sentry/flutter/SentryFlutterTest.kt | 0 .../flutter}/class-diagram.svg | 0 .../flutter}/config/detekt-bl.xml | 0 .../flutter}/dartdoc_options.yaml | 0 .../flutter}/example/.gitignore | 0 .../flutter}/example/.metadata | 0 .../flutter}/example/README.md | 0 .../flutter}/example/analysis_options.yaml | 0 .../flutter}/example/android/.gitignore | 0 .../example/android/app/CMakeLists.txt | 0 .../flutter}/example/android/app/build.gradle | 0 .../example/android/app/proguard-rules.pro | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../app/src/main/cpp/native-sample.cpp | 0 .../io/sentry/samples/flutter/MainActivity.kt | 0 .../main/res/drawable/launch_background.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/values/styles.xml | 0 .../android/app/src/main/res/xml/network.xml | 0 .../flutter}/example/android/build.gradle | 0 .../example/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../example/android/sentry.properties | 0 .../flutter}/example/android/settings.gradle | 0 .../flutter}/example/assets/lorem-ipsum.txt | 0 .../example/assets/sentry-wordmark.png | Bin .../flutter}/example/devtools_options.yaml | 0 .../flutter}/example/img/ios_simulator.png | Bin .../flutter}/example/img/sentry_dashboard.png | Bin .../example/integration_test/all.dart | 0 .../integration_test/integration_test.dart | 0 .../integration_test/profiling_test.dart | 0 .../example/integration_test/replay_test.dart | 0 .../sentry_widgets_flutter_binding_test.dart | 0 .../integration_test/test_driver/driver.dart | 0 .../example/integration_test/utils.dart | 0 .../integration_test/web_sdk_test.dart | 0 .../flutter}/example/ios/.gitignore | 0 .../ios/Flutter/AppFrameworkInfo.plist | 0 .../example/ios/Flutter/Debug.xcconfig | 0 .../example/ios/Flutter/Release.xcconfig | 0 .../flutter}/example/ios/Gemfile | 0 .../flutter}/example/ios/Gemfile.lock | 0 .../flutter}/example/ios/Podfile | 0 .../ios/Runner.xcodeproj/project.pbxproj | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../example/ios/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../LaunchImage.imageset/README.md | 0 .../Runner/Base.lproj/LaunchScreen.storyboard | 0 .../ios/Runner/Base.lproj/Main.storyboard | 0 .../flutter}/example/ios/Runner/Buggy.h | 0 .../flutter}/example/ios/Runner/Buggy.m | 0 .../flutter}/example/ios/Runner/Info.plist | 0 .../ios/Runner/Runner-Bridging-Header.h | 0 .../example/ios/Runner/Runner.entitlements | 0 .../ios/Runner/RunnerRelease.entitlements | 0 .../ios/RunnerTests/SentryFlutterTests.swift | 0 .../flutter}/example/ios/fastlane/Appfile | 0 .../flutter}/example/ios/fastlane/Fastfile | 0 .../flutter}/example/ios/fastlane/Matchfile | 0 .../example/lib/auto_close_screen.dart | 0 .../lib/drift/connection/connection.dart | 0 .../example/lib/drift/connection/native.dart | 0 .../lib/drift/connection/unsupported.dart | 0 .../flutter}/example/lib/drift/database.dart | 0 .../example/lib/drift/database.g.dart | 0 .../flutter}/example/lib/isar/user.dart | 0 .../flutter}/example/lib/isar/user.g.dart | 0 .../flutter}/example/lib/main.dart | 0 .../flutter}/example/linux/.gitignore | 0 .../flutter}/example/linux/CMakeLists.txt | 0 .../example/linux/flutter/CMakeLists.txt | 0 .../flutter}/example/linux/main.cc | 0 .../flutter}/example/linux/my_application.cc | 0 .../flutter}/example/linux/my_application.h | 0 .../flutter}/example/macos/.gitignore | 0 .../macos/Flutter/Flutter-Debug.xcconfig | 0 .../macos/Flutter/Flutter-Release.xcconfig | 0 .../flutter}/example/macos/Podfile | 0 .../macos/Runner.xcodeproj/project.pbxproj | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../example/macos/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/app_icon_1024.png | Bin .../AppIcon.appiconset/app_icon_128.png | Bin .../AppIcon.appiconset/app_icon_16.png | Bin .../AppIcon.appiconset/app_icon_256.png | Bin .../AppIcon.appiconset/app_icon_32.png | Bin .../AppIcon.appiconset/app_icon_512.png | Bin .../AppIcon.appiconset/app_icon_64.png | Bin .../macos/Runner/Base.lproj/MainMenu.xib | 0 .../flutter}/example/macos/Runner/Buggy.h | 0 .../flutter}/example/macos/Runner/Buggy.m | 0 .../macos/Runner/Configs/AppInfo.xcconfig | 0 .../macos/Runner/Configs/Debug.xcconfig | 0 .../macos/Runner/Configs/Release.xcconfig | 0 .../macos/Runner/Configs/Warnings.xcconfig | 0 .../macos/Runner/DebugProfile.entitlements | 0 .../flutter}/example/macos/Runner/Info.plist | 0 .../macos/Runner/MainFlutterWindow.swift | 0 .../example/macos/Runner/Release.entitlements | 0 .../macos/Runner/Runner-Bridging-Header.h | 0 .../flutter}/example/pubspec.yaml | 0 .../flutter}/example/pubspec_overrides.yaml | 0 {flutter => packages/flutter}/example/run.sh | 0 .../flutter}/example/web/favicon.png | Bin .../flutter}/example/web/icons/Icon-192.png | Bin .../flutter}/example/web/icons/Icon-512.png | Bin .../flutter}/example/web/index.html | 0 .../flutter}/example/web/manifest.json | 0 .../flutter/example}/windows/.gitignore | 0 .../flutter}/example/windows/CMakeLists.txt | 216 +++---- .../example/windows/flutter/.gitignore | 0 .../example}/windows/flutter/CMakeLists.txt | 0 .../example}/windows/runner/CMakeLists.txt | 0 .../flutter}/example/windows/runner/Runner.rc | 242 ++++---- .../windows/runner/flutter_window.cpp | 0 .../example}/windows/runner/flutter_window.h | 0 .../flutter}/example/windows/runner/main.cpp | 86 +-- .../example}/windows/runner/resource.h | 0 .../windows/runner/resources/app_icon.ico | Bin .../windows/runner/runner.exe.manifest | 40 +- .../flutter}/example/windows/runner/utils.cpp | 130 ++-- .../flutter/example}/windows/runner/utils.h | 0 .../example}/windows/runner/win32_window.cpp | 0 .../example}/windows/runner/win32_window.h | 0 {flutter => packages/flutter}/ffi-cocoa.yaml | 0 {flutter => packages/flutter}/ffi-jni.yaml | 0 {flutter => packages/flutter}/ffi-native.yaml | 0 {flutter => packages/flutter}/ios/.gitignore | 0 .../flutter}/ios/sentry_flutter.podspec | 0 .../flutter}/ios/sentry_flutter/Package.swift | 0 .../sentry_flutter/SentryFlutter.swift | 0 .../sentry_flutter/SentryFlutterPlugin.swift | 0 .../SentryFlutterReplayBreadcrumbConverter.m | 0 .../SentryFlutterReplayScreenshotProvider.m | 0 .../SentryFlutterReplayBreadcrumbConverter.h | 0 .../SentryFlutterReplayScreenshotProvider.h | 0 .../flutter}/lib/sentry_flutter.dart | 0 .../flutter}/lib/sentry_flutter_web.dart | 0 .../flutter}/lib/src/binding_wrapper.dart | 0 ...id_platform_exception_event_processor.dart | 0 .../flutter_enricher_event_processor.dart | 0 .../flutter_exception_event_processor.dart | 0 .../platform_exception_event_processor.dart | 0 .../replay_event_processor.dart | 0 .../screenshot_event_processor.dart | 0 .../io_url_filter_event_processor.dart | 0 .../url_filter_event_processor.dart | 0 .../web_url_filter_event_processor.dart | 0 .../widget_event_processor.dart | 0 .../src/feedback/sentry_feedback_options.dart | 0 .../src/feedback/sentry_feedback_widget.dart | 0 .../lib/src/feedback/sentry_logo.dart | 0 .../lib/src/file_system_transport.dart | 0 .../flutter_exception_type_identifier.dart | 0 .../lib/src/flutter_sentry_attachment.dart | 0 .../lib/src/frame_callback_handler.dart | 0 .../sentry_delayed_frames_tracker.dart | 0 .../span_frame_metrics_collector.dart | 0 .../connectivity_integration.dart | 0 .../connectivity/connectivity_provider.dart | 0 .../noop_connectivity_provider.dart | 0 .../web_connectivity_provider.dart | 0 .../integrations/debug_print_integration.dart | 0 .../flutter_error_integration.dart | 0 ...er_framework_feature_flag_integration.dart | 0 .../frames_tracking_integration.dart | 0 .../generic_app_start_integration.dart | 0 .../lib/src/integrations/integrations.dart | 0 .../load_contexts_integration.dart | 0 .../load_debug_images_integration.dart | 0 .../load_release_integration.dart | 0 .../native_app_start_handler.dart | 0 .../native_app_start_integration.dart | 0 .../native_load_debug_images_integration.dart | 0 .../integrations/native_sdk_integration.dart | 0 .../integrations/on_error_integration.dart | 0 .../integrations/screenshot_integration.dart | 0 .../lib/src/integrations/sdk_integration.dart | 0 .../web_load_debug_images_integration.dart | 0 .../src/integrations/web_sdk_integration.dart | 0 .../integrations/web_session_integration.dart | 0 .../widgets_binding_integration.dart | 0 .../widgets_flutter_binding_integration.dart | 0 .../flutter}/lib/src/jvm/jvm_exception.dart | 0 .../flutter}/lib/src/jvm/jvm_frame.dart | 0 .../flutter}/lib/src/native/c/binding.dart | 0 .../lib/src/native/c/sentry_native.dart | 0 .../flutter}/lib/src/native/c/utils.dart | 0 .../lib/src/native/cocoa/binding.dart | 0 .../native/cocoa/cocoa_replay_recorder.dart | 0 .../src/native/cocoa/sentry_native_cocoa.dart | 0 .../flutter}/lib/src/native/factory.dart | 0 .../flutter}/lib/src/native/factory_real.dart | 0 .../flutter}/lib/src/native/factory_web.dart | 0 .../native/java/android_replay_recorder.dart | 0 .../flutter}/lib/src/native/java/binding.dart | 0 .../src/native/java/sentry_native_java.dart | 0 .../lib/src/native/method_channel_helper.dart | 0 .../lib/src/native/native_app_start.dart | 0 .../lib/src/native/native_memory.dart | 0 .../lib/src/native/native_scope_observer.dart | 0 .../lib/src/native/sentry_native_binding.dart | 0 .../lib/src/native/sentry_native_channel.dart | 0 .../lib/src/native/sentry_native_invoker.dart | 0 .../native/sentry_safe_method_channel.dart | 0 .../lib/src/navigation/sentry_display.dart | 0 .../src/navigation/sentry_display_widget.dart | 0 .../navigation/sentry_navigator_observer.dart | 0 .../navigation/time_to_display_tracker.dart | 0 .../time_to_full_display_tracker.dart | 0 .../time_to_initial_display_tracker.dart | 0 .../flutter}/lib/src/profiling.dart | 0 .../lib/src/renderer/io_renderer.dart | 0 .../flutter}/lib/src/renderer/renderer.dart | 0 .../lib/src/renderer/unknown_renderer.dart | 0 .../lib/src/renderer/web_renderer.dart | 0 .../flutter}/lib/src/replay/integration.dart | 0 .../lib/src/replay/replay_config.dart | 0 .../lib/src/replay/replay_quality.dart | 0 .../lib/src/replay/replay_recorder.dart | 0 .../lib/src/replay/scheduled_recorder.dart | 0 .../src/replay/scheduled_recorder_config.dart | 0 .../flutter}/lib/src/replay/scheduler.dart | 0 .../lib/src/screenshot/masking_config.dart | 0 .../flutter}/lib/src/screenshot/recorder.dart | 0 .../lib/src/screenshot/recorder_config.dart | 0 .../lib/src/screenshot/screenshot.dart | 0 .../src/screenshot/screenshot_support.dart | 0 .../src/screenshot/sentry_mask_widget.dart | 0 .../screenshot/sentry_screenshot_quality.dart | 0 .../screenshot/sentry_screenshot_widget.dart | 0 .../src/screenshot/sentry_unmask_widget.dart | 0 .../lib/src/screenshot/widget_filter.dart | 0 .../flutter}/lib/src/sentry_asset_bundle.dart | 0 .../flutter}/lib/src/sentry_flutter.dart | 0 .../lib/src/sentry_flutter_options.dart | 0 .../lib/src/sentry_privacy_options.dart | 0 .../lib/src/sentry_replay_options.dart | 0 .../flutter}/lib/src/sentry_widget.dart | 0 .../sentry_user_interaction_widget.dart | 0 .../user_interaction_info.dart | 0 .../flutter}/lib/src/utils/debouncer.dart | 0 .../utils/platform_dispatcher_wrapper.dart | 0 .../lib/src/utils/timer_debouncer.dart | 0 .../flutter}/lib/src/version.dart | 0 .../view_hierarchy/sentry_tree_walker.dart | 0 .../view_hierarchy_event_processor.dart | 0 .../view_hierarchy_integration.dart | 0 .../lib/src/web/javascript_transport.dart | 0 .../lib/src/web/noop_sentry_js_binding.dart | 0 .../script_loader/noop_script_dom_api.dart | 0 .../src/web/script_loader/script_dom_api.dart | 0 .../script_loader/sentry_script_loader.dart | 0 .../web/script_loader/web_script_dom_api.dart | 0 .../lib/src/web/sentry_js_binding.dart | 0 .../lib/src/web/sentry_js_bundle.dart | 0 .../lib/src/web/sentry_js_sdk_version.dart | 0 .../flutter}/lib/src/web/sentry_web.dart | 0 .../lib/src/web/web_sentry_js_binding.dart | 0 .../lib/src/web/web_session_handler.dart | 0 .../flutter}/lib/src/widget_utils.dart | 0 .../lib/src/widgets_binding_observer.dart | 0 .../flutter}/linux/CMakeLists.txt | 0 .../sentry_flutter/sentry_flutter_plugin.h | 0 .../flutter}/macos/sentry_flutter.podspec | 0 .../macos/sentry_flutter/Package.swift | 0 .../sentry_flutter/SentryFlutter.swift | 0 .../sentry_flutter/SentryFlutterPlugin.swift | 0 .../SentryFlutterReplayBreadcrumbConverter.m | 0 .../SentryFlutterReplayScreenshotProvider.m | 0 .../SentryFlutterReplayBreadcrumbConverter.h | 0 .../SentryFlutterReplayScreenshotProvider.h | 0 .../flutter}/microbenchmarks/.gitignore | 0 .../flutter}/microbenchmarks/.metadata | 0 .../flutter}/microbenchmarks/README.md | 0 .../microbenchmarks/analysis_options.yaml | 0 .../microbenchmarks/android/.gitignore | 0 .../microbenchmarks/android/app/build.gradle | 0 .../android/app/src/debug/AndroidManifest.xml | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../example/microbenchmarks/MainActivity.kt | 0 .../res/drawable-v21/launch_background.xml | 0 .../main/res/drawable/launch_background.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/values-night/styles.xml | 0 .../app/src/main/res/values/styles.xml | 0 .../app/src/profile/AndroidManifest.xml | 0 .../microbenchmarks/android/build.gradle | 0 .../microbenchmarks/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../microbenchmarks/android/settings.gradle | 0 .../flutter}/microbenchmarks/ios/.gitignore | 0 .../ios/Flutter/AppFrameworkInfo.plist | 0 .../ios/Flutter/Debug.xcconfig | 0 .../ios/Flutter/Release.xcconfig | 0 .../flutter}/microbenchmarks/ios/Podfile | 0 .../ios/Runner.xcodeproj/project.pbxproj | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../ios/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../LaunchImage.imageset/README.md | 0 .../Runner/Base.lproj/LaunchScreen.storyboard | 0 .../ios/Runner/Base.lproj/Main.storyboard | 0 .../microbenchmarks/ios/Runner/Info.plist | 0 .../ios/Runner/Runner-Bridging-Header.h | 0 .../ios/RunnerTests/RunnerTests.swift | 0 .../flutter}/microbenchmarks/lib/main.dart | 0 .../microbenchmarks/lib/src/image_bench.dart | 0 .../microbenchmarks/lib/src/jni_bench.dart | 0 .../microbenchmarks/lib/src/memory_bench.dart | 0 .../flutter}/microbenchmarks/linux/.gitignore | 0 .../microbenchmarks/linux/CMakeLists.txt | 0 .../linux/flutter/CMakeLists.txt | 0 .../flutter/generated_plugin_registrant.cc | 0 .../flutter/generated_plugin_registrant.h | 0 .../linux/flutter/generated_plugins.cmake | 0 .../linux/runner/CMakeLists.txt | 0 .../microbenchmarks/linux/runner/main.cc | 0 .../linux/runner/my_application.cc | 0 .../linux/runner/my_application.h | 0 .../flutter}/microbenchmarks/macos/.gitignore | 0 .../macos/Flutter/Flutter-Debug.xcconfig | 0 .../macos/Flutter/Flutter-Release.xcconfig | 0 .../flutter}/microbenchmarks/macos/Podfile | 0 .../macos/Runner.xcodeproj/project.pbxproj | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../macos/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/app_icon_1024.png | Bin .../AppIcon.appiconset/app_icon_128.png | Bin .../AppIcon.appiconset/app_icon_16.png | Bin .../AppIcon.appiconset/app_icon_256.png | Bin .../AppIcon.appiconset/app_icon_32.png | Bin .../AppIcon.appiconset/app_icon_512.png | Bin .../AppIcon.appiconset/app_icon_64.png | Bin .../macos/Runner/Base.lproj/MainMenu.xib | 0 .../macos/Runner/Configs/AppInfo.xcconfig | 0 .../macos/Runner/Configs/Debug.xcconfig | 0 .../macos/Runner/Configs/Release.xcconfig | 0 .../macos/Runner/Configs/Warnings.xcconfig | 0 .../macos/Runner/DebugProfile.entitlements | 0 .../microbenchmarks/macos/Runner/Info.plist | 0 .../macos/Runner/MainFlutterWindow.swift | 0 .../macos/Runner/Release.entitlements | 0 .../macos/RunnerTests/RunnerTests.swift | 0 .../flutter}/microbenchmarks/pubspec.yaml | 0 .../flutter}/microbenchmarks/web/favicon.png | Bin .../microbenchmarks/web/icons/Icon-192.png | Bin .../microbenchmarks/web/icons/Icon-512.png | Bin .../web/icons/Icon-maskable-192.png | Bin .../web/icons/Icon-maskable-512.png | Bin .../flutter}/microbenchmarks/web/index.html | 0 .../microbenchmarks/web/manifest.json | 0 .../microbenchmarks}/windows/.gitignore | 34 +- .../microbenchmarks/windows/CMakeLists.txt | 0 .../windows/flutter/CMakeLists.txt | 218 +++---- .../flutter/generated_plugin_registrant.cc | 0 .../flutter/generated_plugin_registrant.h | 0 .../windows/flutter/generated_plugins.cmake | 0 .../windows/runner/CMakeLists.txt | 80 +-- .../microbenchmarks/windows/runner/Runner.rc | 0 .../windows/runner/flutter_window.cpp | 142 ++--- .../windows/runner/flutter_window.h | 66 +- .../microbenchmarks/windows/runner/main.cpp | 0 .../windows/runner/resource.h | 32 +- .../windows/runner/resources/app_icon.ico | Bin .../windows/runner/runner.exe.manifest | 0 .../microbenchmarks/windows/runner/utils.cpp | 0 .../microbenchmarks}/windows/runner/utils.h | 38 +- .../windows/runner/win32_window.cpp | 576 +++++++++--------- .../windows/runner/win32_window.h | 204 +++---- {flutter => packages/flutter}/pubspec.yaml | 0 .../flutter}/pubspec_overrides.yaml | 0 .../scripts/generate-cocoa-bindings.sh | 0 .../flutter}/scripts/generate-jni-bindings.sh | 0 .../scripts/generate-native-bindings.ps1 | 0 .../flutter}/scripts/update-android.sh | 0 .../flutter}/scripts/update-cocoa.sh | 0 .../flutter}/scripts/update-js.sh | 0 .../flutter}/scripts/update-native.sh | 0 .../flutter}/sentry-native/CMakeCache.txt | 0 .../sentry-native/sentry-native.cmake | 0 ...atform_exception_event_processor_test.dart | 0 .../flutter}/test/binding.dart | 0 ...flutter_enricher_event_processor_test.dart | 0 ...lutter_exception_event_processor_test.dart | 0 ...atform_exception_event_processor_test.dart | 0 .../screenshot_event_processor_test.dart | 0 .../io_filter_event_processor_test.dart | 0 .../web_url_filter_event_processor_test.dart | 0 .../widget_event_processor_test.dart | 0 .../test/fake_frame_callback_handler.dart | 0 .../feedback/sentry_feedback_widget_test.dart | 0 .../test/file_system_transport_test.dart | 0 .../test/flutter_sentry_attachment_test.dart | 0 .../flutter}/test/flutter_version_test.dart | 0 .../frames_tracking_integration_test.dart | 0 .../sentry_delayed_frames_tracker_test.dart | 0 .../span_frame_metrics_collector_test.dart | 0 .../span_frame_metrics_test.dart | 0 .../flutter}/test/initialization_test.dart | 0 .../connectivity_integration_test.dart | 0 .../debug_print_integration_test.dart | 0 .../flutter}/test/integrations/fixture.dart | 0 .../flutter_error_integration_test.dart | 0 ...amework_feature_flag_integration_test.dart | 0 .../generic_app_start_integration_test.dart | 0 .../integrations/init_native_sdk_test.dart | 0 .../load_contexts_integration_test.dart | 0 .../load_contexts_integrations_test.dart | 0 ..._native_debug_images_integration_test.dart | 0 .../load_release_integration_test.dart | 0 .../mock_platform_dispatcher.dart | 0 .../native_app_start_handler_test.dart | 0 .../native_app_start_integration_test.dart | 0 .../native_sdk_integration_test.dart | 0 ...ets_binding_on_error_integration_test.dart | 0 .../not_initialized_widgets_binding_test.dart | 0 .../on_error_integration_test.dart | 0 .../screenshot_integration_test.dart | 0 .../web_sdk_integration_test.dart | 0 .../web_session_integration_test.dart | 0 .../widgets_binding_integration_test.dart | 0 ...gets_flutter_binding_integration_test.dart | 0 .../flutter}/test/jvm/jvm_exception_test.dart | 0 .../flutter}/test/jvm/jvm_frame_test.dart | 0 .../test/method_channel_helper_test.dart | 0 {flutter => packages/flutter}/test/mocks.dart | 0 .../flutter}/test/mocks.mocks.dart | 0 .../flutter}/test/native_memory_test.dart | 0 .../flutter}/test/native_memory_web_mock.dart | 0 .../test/native_scope_observer_test.dart | 0 .../test/navigation/sentry_display_test.dart | 0 .../sentry_display_widget_test.dart | 0 .../sentry_navigator_observer_test.dart | 0 ...sentry_navigator_observer_traces_test.dart | 0 .../time_to_display_tracker_test.dart | 0 .../time_to_full_display_tracker_test.dart | 0 .../time_to_initial_display_tracker_test.dart | 0 .../test/no_such_method_provider.dart | 0 .../flutter}/test/profiling_test.dart | 0 .../replay/android_replay_recorder_web.dart | 0 .../replay/replay_event_processor_test.dart | 0 .../test/replay/replay_integration_test.dart | 0 .../test/replay/replay_native_test.dart | 0 .../test/replay/replay_test_util.dart | 0 .../test/replay/scheduled_recorder_test.dart | 0 .../flutter}/test/replay/scheduler_test.dart | 0 .../test/screenshot/masking_config_test.dart | 0 .../test/screenshot/recorder_config_test.dart | 0 .../test/screenshot/recorder_test.dart | 0 .../screenshot/screenshot_support_test.dart | 0 .../sentry_screenshot_quality_test.dart | 0 .../sentry_screenshot_widget_test.dart | 0 .../sentry_screenshot_widget_test.mocks.dart | 0 .../flutter}/test/screenshot/test_widget.dart | 0 .../test/screenshot/widget_filter_test.dart | 0 .../test/sentry_asset_bundle_test.dart | 0 .../test/sentry_flutter_options_test.dart | 0 .../flutter}/test/sentry_flutter_test.dart | 0 .../flutter}/test/sentry_flutter_util.dart | 0 .../sentry_native/sentry_native_test.dart | 0 .../sentry_native/sentry_native_test_ffi.dart | 0 .../sentry_native/sentry_native_test_web.dart | 0 .../test/sentry_native_channel_test.dart | 0 .../flutter}/test/sentry_widget_test.dart | 0 .../sentry_widgets_binding_mixin_test.dart | 0 .../sentry_widgets_flutter_binding_test.dart | 0 .../sentry_user_interaction_widget_test.dart | 0 .../flutter}/test/utils/debouncer_test.dart | 0 .../flutter}/test/version_test.dart | 0 .../sentry_tree_walker_test.dart | 0 .../view_hierarchy_event_processor_test.dart | 0 .../view_hierarchy_integration_test.dart | 0 .../test/web/sentry_js_bundles_test.dart | 0 .../test/web/sentry_script_loader_test.dart | 0 .../sentry_script_loader_tt_custom_test.dart | 0 ...entry_script_loader_tt_forbidden_test.dart | 0 .../flutter}/test/web/sentry_web_test.dart | 0 .../flutter}/test/web/utils.dart | 0 .../test/web/web_sentry_js_binding_test.dart | 0 .../test/web/web_session_handler_test.dart | 0 .../test/widgets_binding_observer_test.dart | 0 .../flutter}/tool/presubmit.sh | 0 .../flutter}/windows/.gitignore | 34 +- .../flutter}/windows/CMakeLists.txt | 38 +- .../sentry_flutter/sentry_flutter_plugin.h | 0 574 files changed, 1088 insertions(+), 1095 deletions(-) delete mode 100644 flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename {flutter => packages/flutter}/.editorconfig (100%) rename {flutter => packages/flutter}/.gitignore (100%) rename {flutter => packages/flutter}/.metadata (100%) rename {flutter => packages/flutter}/.pubignore (100%) rename {flutter => packages/flutter}/CHANGELOG.md (100%) rename {flutter => packages/flutter}/LICENSE (100%) rename {flutter => packages/flutter}/README.md (100%) rename {flutter => packages/flutter}/analysis_options.yaml (100%) rename {flutter => packages/flutter}/android/.gitignore (100%) rename {flutter => packages/flutter}/android/build.gradle (100%) rename {flutter => packages/flutter}/android/gradle.properties (100%) rename {flutter => packages/flutter}/android/gradle/wrapper/gradle-wrapper.properties (100%) rename {flutter => packages/flutter}/android/proguard-rules.pro (100%) rename {flutter => packages/flutter}/android/settings.gradle (100%) rename {flutter => packages/flutter}/android/src/main/AndroidManifest.xml (100%) rename {flutter => packages/flutter}/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt (100%) rename {flutter => packages/flutter}/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt (100%) rename {flutter => packages/flutter}/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayBreadcrumbConverter.kt (100%) rename {flutter => packages/flutter}/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayRecorder.kt (100%) rename {flutter => packages/flutter}/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt (100%) rename {flutter => packages/flutter}/class-diagram.svg (100%) rename {flutter => packages/flutter}/config/detekt-bl.xml (100%) rename {flutter => packages/flutter}/dartdoc_options.yaml (100%) rename {flutter => packages/flutter}/example/.gitignore (100%) rename {flutter => packages/flutter}/example/.metadata (100%) rename {flutter => packages/flutter}/example/README.md (100%) rename {flutter => packages/flutter}/example/analysis_options.yaml (100%) rename {flutter => packages/flutter}/example/android/.gitignore (100%) rename {flutter => packages/flutter}/example/android/app/CMakeLists.txt (100%) rename {flutter => packages/flutter}/example/android/app/build.gradle (100%) rename {flutter => packages/flutter}/example/android/app/proguard-rules.pro (100%) rename {flutter => packages/flutter}/example/android/app/src/main/AndroidManifest.xml (100%) rename {flutter => packages/flutter}/example/android/app/src/main/cpp/native-sample.cpp (100%) rename {flutter => packages/flutter}/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt (100%) rename {flutter => packages/flutter}/example/android/app/src/main/res/drawable/launch_background.xml (100%) rename {flutter => packages/flutter}/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {flutter => packages/flutter}/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {flutter => packages/flutter}/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {flutter => packages/flutter}/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {flutter => packages/flutter}/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {flutter => packages/flutter}/example/android/app/src/main/res/values/styles.xml (100%) rename {flutter => packages/flutter}/example/android/app/src/main/res/xml/network.xml (100%) rename {flutter => packages/flutter}/example/android/build.gradle (100%) rename {flutter => packages/flutter}/example/android/gradle.properties (100%) rename {flutter => packages/flutter}/example/android/gradle/wrapper/gradle-wrapper.properties (100%) rename {flutter => packages/flutter}/example/android/sentry.properties (100%) rename {flutter => packages/flutter}/example/android/settings.gradle (100%) rename {flutter => packages/flutter}/example/assets/lorem-ipsum.txt (100%) rename {flutter => packages/flutter}/example/assets/sentry-wordmark.png (100%) rename {flutter => packages/flutter}/example/devtools_options.yaml (100%) rename {flutter => packages/flutter}/example/img/ios_simulator.png (100%) rename {flutter => packages/flutter}/example/img/sentry_dashboard.png (100%) rename {flutter => packages/flutter}/example/integration_test/all.dart (100%) rename {flutter => packages/flutter}/example/integration_test/integration_test.dart (100%) rename {flutter => packages/flutter}/example/integration_test/profiling_test.dart (100%) rename {flutter => packages/flutter}/example/integration_test/replay_test.dart (100%) rename {flutter => packages/flutter}/example/integration_test/sentry_widgets_flutter_binding_test.dart (100%) rename {flutter => packages/flutter}/example/integration_test/test_driver/driver.dart (100%) rename {flutter => packages/flutter}/example/integration_test/utils.dart (100%) rename {flutter => packages/flutter}/example/integration_test/web_sdk_test.dart (100%) rename {flutter => packages/flutter}/example/ios/.gitignore (100%) rename {flutter => packages/flutter}/example/ios/Flutter/AppFrameworkInfo.plist (100%) rename {flutter => packages/flutter}/example/ios/Flutter/Debug.xcconfig (100%) rename {flutter => packages/flutter}/example/ios/Flutter/Release.xcconfig (100%) rename {flutter => packages/flutter}/example/ios/Gemfile (100%) rename {flutter => packages/flutter}/example/ios/Gemfile.lock (100%) rename {flutter => packages/flutter}/example/ios/Podfile (100%) rename {flutter => packages/flutter}/example/ios/Runner.xcodeproj/project.pbxproj (100%) rename {flutter => packages/flutter}/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename {flutter => packages/flutter}/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {flutter => packages/flutter}/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {flutter => packages/flutter}/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {flutter => packages/flutter}/example/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {flutter => packages/flutter}/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {flutter => packages/flutter}/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {flutter => packages/flutter}/example/ios/Runner/AppDelegate.swift (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename {flutter => packages/flutter}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename {flutter => packages/flutter}/example/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename {flutter => packages/flutter}/example/ios/Runner/Base.lproj/Main.storyboard (100%) rename {flutter => packages/flutter}/example/ios/Runner/Buggy.h (100%) rename {flutter => packages/flutter}/example/ios/Runner/Buggy.m (100%) rename {flutter => packages/flutter}/example/ios/Runner/Info.plist (100%) rename {flutter => packages/flutter}/example/ios/Runner/Runner-Bridging-Header.h (100%) rename {flutter => packages/flutter}/example/ios/Runner/Runner.entitlements (100%) rename {flutter => packages/flutter}/example/ios/Runner/RunnerRelease.entitlements (100%) rename {flutter => packages/flutter}/example/ios/RunnerTests/SentryFlutterTests.swift (100%) rename {flutter => packages/flutter}/example/ios/fastlane/Appfile (100%) rename {flutter => packages/flutter}/example/ios/fastlane/Fastfile (100%) rename {flutter => packages/flutter}/example/ios/fastlane/Matchfile (100%) rename {flutter => packages/flutter}/example/lib/auto_close_screen.dart (100%) rename {flutter => packages/flutter}/example/lib/drift/connection/connection.dart (100%) rename {flutter => packages/flutter}/example/lib/drift/connection/native.dart (100%) rename {flutter => packages/flutter}/example/lib/drift/connection/unsupported.dart (100%) rename {flutter => packages/flutter}/example/lib/drift/database.dart (100%) rename {flutter => packages/flutter}/example/lib/drift/database.g.dart (100%) rename {flutter => packages/flutter}/example/lib/isar/user.dart (100%) rename {flutter => packages/flutter}/example/lib/isar/user.g.dart (100%) rename {flutter => packages/flutter}/example/lib/main.dart (100%) rename {flutter => packages/flutter}/example/linux/.gitignore (100%) rename {flutter => packages/flutter}/example/linux/CMakeLists.txt (100%) rename {flutter => packages/flutter}/example/linux/flutter/CMakeLists.txt (100%) rename {flutter => packages/flutter}/example/linux/main.cc (100%) rename {flutter => packages/flutter}/example/linux/my_application.cc (100%) rename {flutter => packages/flutter}/example/linux/my_application.h (100%) rename {flutter => packages/flutter}/example/macos/.gitignore (100%) rename {flutter => packages/flutter}/example/macos/Flutter/Flutter-Debug.xcconfig (100%) rename {flutter => packages/flutter}/example/macos/Flutter/Flutter-Release.xcconfig (100%) rename {flutter => packages/flutter}/example/macos/Podfile (100%) rename {flutter => packages/flutter}/example/macos/Runner.xcodeproj/project.pbxproj (100%) rename {flutter => packages/flutter}/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {flutter => packages/flutter}/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {flutter => packages/flutter}/example/macos/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {flutter => packages/flutter}/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {flutter => packages/flutter}/example/macos/Runner/AppDelegate.swift (100%) rename {flutter => packages/flutter}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {flutter => packages/flutter}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png (100%) rename {flutter => packages/flutter}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png (100%) rename {flutter => packages/flutter}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png (100%) rename {flutter => packages/flutter}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png (100%) rename {flutter => packages/flutter}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png (100%) rename {flutter => packages/flutter}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png (100%) rename {flutter => packages/flutter}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png (100%) rename {flutter => packages/flutter}/example/macos/Runner/Base.lproj/MainMenu.xib (100%) rename {flutter => packages/flutter}/example/macos/Runner/Buggy.h (100%) rename {flutter => packages/flutter}/example/macos/Runner/Buggy.m (100%) rename {flutter => packages/flutter}/example/macos/Runner/Configs/AppInfo.xcconfig (100%) rename {flutter => packages/flutter}/example/macos/Runner/Configs/Debug.xcconfig (100%) rename {flutter => packages/flutter}/example/macos/Runner/Configs/Release.xcconfig (100%) rename {flutter => packages/flutter}/example/macos/Runner/Configs/Warnings.xcconfig (100%) rename {flutter => packages/flutter}/example/macos/Runner/DebugProfile.entitlements (100%) rename {flutter => packages/flutter}/example/macos/Runner/Info.plist (100%) rename {flutter => packages/flutter}/example/macos/Runner/MainFlutterWindow.swift (100%) rename {flutter => packages/flutter}/example/macos/Runner/Release.entitlements (100%) rename {flutter => packages/flutter}/example/macos/Runner/Runner-Bridging-Header.h (100%) rename {flutter => packages/flutter}/example/pubspec.yaml (100%) rename {flutter => packages/flutter}/example/pubspec_overrides.yaml (100%) rename {flutter => packages/flutter}/example/run.sh (100%) rename {flutter => packages/flutter}/example/web/favicon.png (100%) rename {flutter => packages/flutter}/example/web/icons/Icon-192.png (100%) rename {flutter => packages/flutter}/example/web/icons/Icon-512.png (100%) rename {flutter => packages/flutter}/example/web/index.html (100%) rename {flutter => packages/flutter}/example/web/manifest.json (100%) rename {flutter/microbenchmarks => packages/flutter/example}/windows/.gitignore (100%) rename {flutter => packages/flutter}/example/windows/CMakeLists.txt (97%) rename {flutter => packages/flutter}/example/windows/flutter/.gitignore (100%) rename {flutter/microbenchmarks => packages/flutter/example}/windows/flutter/CMakeLists.txt (100%) rename {flutter/microbenchmarks => packages/flutter/example}/windows/runner/CMakeLists.txt (100%) rename {flutter => packages/flutter}/example/windows/runner/Runner.rc (96%) rename {flutter/microbenchmarks => packages/flutter/example}/windows/runner/flutter_window.cpp (100%) rename {flutter/microbenchmarks => packages/flutter/example}/windows/runner/flutter_window.h (100%) rename {flutter => packages/flutter}/example/windows/runner/main.cpp (96%) rename {flutter/microbenchmarks => packages/flutter/example}/windows/runner/resource.h (100%) rename {flutter => packages/flutter}/example/windows/runner/resources/app_icon.ico (100%) rename {flutter => packages/flutter}/example/windows/runner/runner.exe.manifest (97%) rename {flutter => packages/flutter}/example/windows/runner/utils.cpp (96%) rename {flutter/microbenchmarks => packages/flutter/example}/windows/runner/utils.h (100%) rename {flutter/microbenchmarks => packages/flutter/example}/windows/runner/win32_window.cpp (100%) rename {flutter/microbenchmarks => packages/flutter/example}/windows/runner/win32_window.h (100%) rename {flutter => packages/flutter}/ffi-cocoa.yaml (100%) rename {flutter => packages/flutter}/ffi-jni.yaml (100%) rename {flutter => packages/flutter}/ffi-native.yaml (100%) rename {flutter => packages/flutter}/ios/.gitignore (100%) rename {flutter => packages/flutter}/ios/sentry_flutter.podspec (100%) rename {flutter => packages/flutter}/ios/sentry_flutter/Package.swift (100%) rename {flutter => packages/flutter}/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutter.swift (100%) rename {flutter => packages/flutter}/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift (100%) rename {flutter => packages/flutter}/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayBreadcrumbConverter.m (100%) rename {flutter => packages/flutter}/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayScreenshotProvider.m (100%) rename {flutter => packages/flutter}/ios/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayBreadcrumbConverter.h (100%) rename {flutter => packages/flutter}/ios/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayScreenshotProvider.h (100%) rename {flutter => packages/flutter}/lib/sentry_flutter.dart (100%) rename {flutter => packages/flutter}/lib/sentry_flutter_web.dart (100%) rename {flutter => packages/flutter}/lib/src/binding_wrapper.dart (100%) rename {flutter => packages/flutter}/lib/src/event_processor/android_platform_exception_event_processor.dart (100%) rename {flutter => packages/flutter}/lib/src/event_processor/flutter_enricher_event_processor.dart (100%) rename {flutter => packages/flutter}/lib/src/event_processor/flutter_exception_event_processor.dart (100%) rename {flutter => packages/flutter}/lib/src/event_processor/platform_exception_event_processor.dart (100%) rename {flutter => packages/flutter}/lib/src/event_processor/replay_event_processor.dart (100%) rename {flutter => packages/flutter}/lib/src/event_processor/screenshot_event_processor.dart (100%) rename {flutter => packages/flutter}/lib/src/event_processor/url_filter/io_url_filter_event_processor.dart (100%) rename {flutter => packages/flutter}/lib/src/event_processor/url_filter/url_filter_event_processor.dart (100%) rename {flutter => packages/flutter}/lib/src/event_processor/url_filter/web_url_filter_event_processor.dart (100%) rename {flutter => packages/flutter}/lib/src/event_processor/widget_event_processor.dart (100%) rename {flutter => packages/flutter}/lib/src/feedback/sentry_feedback_options.dart (100%) rename {flutter => packages/flutter}/lib/src/feedback/sentry_feedback_widget.dart (100%) rename {flutter => packages/flutter}/lib/src/feedback/sentry_logo.dart (100%) rename {flutter => packages/flutter}/lib/src/file_system_transport.dart (100%) rename {flutter => packages/flutter}/lib/src/flutter_exception_type_identifier.dart (100%) rename {flutter => packages/flutter}/lib/src/flutter_sentry_attachment.dart (100%) rename {flutter => packages/flutter}/lib/src/frame_callback_handler.dart (100%) rename {flutter => packages/flutter}/lib/src/frames_tracking/sentry_delayed_frames_tracker.dart (100%) rename {flutter => packages/flutter}/lib/src/frames_tracking/span_frame_metrics_collector.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/connectivity/connectivity_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/connectivity/connectivity_provider.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/connectivity/noop_connectivity_provider.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/connectivity/web_connectivity_provider.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/debug_print_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/flutter_error_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/flutter_framework_feature_flag_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/frames_tracking_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/generic_app_start_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/integrations.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/load_contexts_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/load_debug_images_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/load_release_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/native_app_start_handler.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/native_app_start_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/native_load_debug_images_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/native_sdk_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/on_error_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/screenshot_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/sdk_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/web_load_debug_images_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/web_sdk_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/web_session_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/widgets_binding_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/integrations/widgets_flutter_binding_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/jvm/jvm_exception.dart (100%) rename {flutter => packages/flutter}/lib/src/jvm/jvm_frame.dart (100%) rename {flutter => packages/flutter}/lib/src/native/c/binding.dart (100%) rename {flutter => packages/flutter}/lib/src/native/c/sentry_native.dart (100%) rename {flutter => packages/flutter}/lib/src/native/c/utils.dart (100%) rename {flutter => packages/flutter}/lib/src/native/cocoa/binding.dart (100%) rename {flutter => packages/flutter}/lib/src/native/cocoa/cocoa_replay_recorder.dart (100%) rename {flutter => packages/flutter}/lib/src/native/cocoa/sentry_native_cocoa.dart (100%) rename {flutter => packages/flutter}/lib/src/native/factory.dart (100%) rename {flutter => packages/flutter}/lib/src/native/factory_real.dart (100%) rename {flutter => packages/flutter}/lib/src/native/factory_web.dart (100%) rename {flutter => packages/flutter}/lib/src/native/java/android_replay_recorder.dart (100%) rename {flutter => packages/flutter}/lib/src/native/java/binding.dart (100%) rename {flutter => packages/flutter}/lib/src/native/java/sentry_native_java.dart (100%) rename {flutter => packages/flutter}/lib/src/native/method_channel_helper.dart (100%) rename {flutter => packages/flutter}/lib/src/native/native_app_start.dart (100%) rename {flutter => packages/flutter}/lib/src/native/native_memory.dart (100%) rename {flutter => packages/flutter}/lib/src/native/native_scope_observer.dart (100%) rename {flutter => packages/flutter}/lib/src/native/sentry_native_binding.dart (100%) rename {flutter => packages/flutter}/lib/src/native/sentry_native_channel.dart (100%) rename {flutter => packages/flutter}/lib/src/native/sentry_native_invoker.dart (100%) rename {flutter => packages/flutter}/lib/src/native/sentry_safe_method_channel.dart (100%) rename {flutter => packages/flutter}/lib/src/navigation/sentry_display.dart (100%) rename {flutter => packages/flutter}/lib/src/navigation/sentry_display_widget.dart (100%) rename {flutter => packages/flutter}/lib/src/navigation/sentry_navigator_observer.dart (100%) rename {flutter => packages/flutter}/lib/src/navigation/time_to_display_tracker.dart (100%) rename {flutter => packages/flutter}/lib/src/navigation/time_to_full_display_tracker.dart (100%) rename {flutter => packages/flutter}/lib/src/navigation/time_to_initial_display_tracker.dart (100%) rename {flutter => packages/flutter}/lib/src/profiling.dart (100%) rename {flutter => packages/flutter}/lib/src/renderer/io_renderer.dart (100%) rename {flutter => packages/flutter}/lib/src/renderer/renderer.dart (100%) rename {flutter => packages/flutter}/lib/src/renderer/unknown_renderer.dart (100%) rename {flutter => packages/flutter}/lib/src/renderer/web_renderer.dart (100%) rename {flutter => packages/flutter}/lib/src/replay/integration.dart (100%) rename {flutter => packages/flutter}/lib/src/replay/replay_config.dart (100%) rename {flutter => packages/flutter}/lib/src/replay/replay_quality.dart (100%) rename {flutter => packages/flutter}/lib/src/replay/replay_recorder.dart (100%) rename {flutter => packages/flutter}/lib/src/replay/scheduled_recorder.dart (100%) rename {flutter => packages/flutter}/lib/src/replay/scheduled_recorder_config.dart (100%) rename {flutter => packages/flutter}/lib/src/replay/scheduler.dart (100%) rename {flutter => packages/flutter}/lib/src/screenshot/masking_config.dart (100%) rename {flutter => packages/flutter}/lib/src/screenshot/recorder.dart (100%) rename {flutter => packages/flutter}/lib/src/screenshot/recorder_config.dart (100%) rename {flutter => packages/flutter}/lib/src/screenshot/screenshot.dart (100%) rename {flutter => packages/flutter}/lib/src/screenshot/screenshot_support.dart (100%) rename {flutter => packages/flutter}/lib/src/screenshot/sentry_mask_widget.dart (100%) rename {flutter => packages/flutter}/lib/src/screenshot/sentry_screenshot_quality.dart (100%) rename {flutter => packages/flutter}/lib/src/screenshot/sentry_screenshot_widget.dart (100%) rename {flutter => packages/flutter}/lib/src/screenshot/sentry_unmask_widget.dart (100%) rename {flutter => packages/flutter}/lib/src/screenshot/widget_filter.dart (100%) rename {flutter => packages/flutter}/lib/src/sentry_asset_bundle.dart (100%) rename {flutter => packages/flutter}/lib/src/sentry_flutter.dart (100%) rename {flutter => packages/flutter}/lib/src/sentry_flutter_options.dart (100%) rename {flutter => packages/flutter}/lib/src/sentry_privacy_options.dart (100%) rename {flutter => packages/flutter}/lib/src/sentry_replay_options.dart (100%) rename {flutter => packages/flutter}/lib/src/sentry_widget.dart (100%) rename {flutter => packages/flutter}/lib/src/user_interaction/sentry_user_interaction_widget.dart (100%) rename {flutter => packages/flutter}/lib/src/user_interaction/user_interaction_info.dart (100%) rename {flutter => packages/flutter}/lib/src/utils/debouncer.dart (100%) rename {flutter => packages/flutter}/lib/src/utils/platform_dispatcher_wrapper.dart (100%) rename {flutter => packages/flutter}/lib/src/utils/timer_debouncer.dart (100%) rename {flutter => packages/flutter}/lib/src/version.dart (100%) rename {flutter => packages/flutter}/lib/src/view_hierarchy/sentry_tree_walker.dart (100%) rename {flutter => packages/flutter}/lib/src/view_hierarchy/view_hierarchy_event_processor.dart (100%) rename {flutter => packages/flutter}/lib/src/view_hierarchy/view_hierarchy_integration.dart (100%) rename {flutter => packages/flutter}/lib/src/web/javascript_transport.dart (100%) rename {flutter => packages/flutter}/lib/src/web/noop_sentry_js_binding.dart (100%) rename {flutter => packages/flutter}/lib/src/web/script_loader/noop_script_dom_api.dart (100%) rename {flutter => packages/flutter}/lib/src/web/script_loader/script_dom_api.dart (100%) rename {flutter => packages/flutter}/lib/src/web/script_loader/sentry_script_loader.dart (100%) rename {flutter => packages/flutter}/lib/src/web/script_loader/web_script_dom_api.dart (100%) rename {flutter => packages/flutter}/lib/src/web/sentry_js_binding.dart (100%) rename {flutter => packages/flutter}/lib/src/web/sentry_js_bundle.dart (100%) rename {flutter => packages/flutter}/lib/src/web/sentry_js_sdk_version.dart (100%) rename {flutter => packages/flutter}/lib/src/web/sentry_web.dart (100%) rename {flutter => packages/flutter}/lib/src/web/web_sentry_js_binding.dart (100%) rename {flutter => packages/flutter}/lib/src/web/web_session_handler.dart (100%) rename {flutter => packages/flutter}/lib/src/widget_utils.dart (100%) rename {flutter => packages/flutter}/lib/src/widgets_binding_observer.dart (100%) rename {flutter => packages/flutter}/linux/CMakeLists.txt (100%) rename {flutter => packages/flutter}/linux/sentry_flutter/sentry_flutter_plugin.h (100%) rename {flutter => packages/flutter}/macos/sentry_flutter.podspec (100%) rename {flutter => packages/flutter}/macos/sentry_flutter/Package.swift (100%) rename {flutter => packages/flutter}/macos/sentry_flutter/Sources/sentry_flutter/SentryFlutter.swift (100%) rename {flutter => packages/flutter}/macos/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift (100%) rename {flutter => packages/flutter}/macos/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayBreadcrumbConverter.m (100%) rename {flutter => packages/flutter}/macos/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayScreenshotProvider.m (100%) rename {flutter => packages/flutter}/macos/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayBreadcrumbConverter.h (100%) rename {flutter => packages/flutter}/macos/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayScreenshotProvider.h (100%) rename {flutter => packages/flutter}/microbenchmarks/.gitignore (100%) rename {flutter => packages/flutter}/microbenchmarks/.metadata (100%) rename {flutter => packages/flutter}/microbenchmarks/README.md (100%) rename {flutter => packages/flutter}/microbenchmarks/analysis_options.yaml (100%) rename {flutter => packages/flutter}/microbenchmarks/android/.gitignore (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/build.gradle (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/debug/AndroidManifest.xml (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/main/AndroidManifest.xml (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/main/kotlin/com/example/microbenchmarks/MainActivity.kt (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/main/res/drawable-v21/launch_background.xml (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/main/res/drawable/launch_background.xml (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/main/res/values-night/styles.xml (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/main/res/values/styles.xml (100%) rename {flutter => packages/flutter}/microbenchmarks/android/app/src/profile/AndroidManifest.xml (100%) rename {flutter => packages/flutter}/microbenchmarks/android/build.gradle (100%) rename {flutter => packages/flutter}/microbenchmarks/android/gradle.properties (100%) rename {flutter => packages/flutter}/microbenchmarks/android/gradle/wrapper/gradle-wrapper.properties (100%) rename {flutter => packages/flutter}/microbenchmarks/android/settings.gradle (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/.gitignore (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Flutter/AppFrameworkInfo.plist (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Flutter/Debug.xcconfig (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Flutter/Release.xcconfig (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Podfile (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner.xcodeproj/project.pbxproj (100%) rename {flutter/ios/sentry_flutter/.swiftpm/xcode/package.xcworkspace => packages/flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace}/contents.xcworkspacedata (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/AppDelegate.swift (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Base.lproj/Main.storyboard (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Info.plist (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/Runner/Runner-Bridging-Header.h (100%) rename {flutter => packages/flutter}/microbenchmarks/ios/RunnerTests/RunnerTests.swift (100%) rename {flutter => packages/flutter}/microbenchmarks/lib/main.dart (100%) rename {flutter => packages/flutter}/microbenchmarks/lib/src/image_bench.dart (100%) rename {flutter => packages/flutter}/microbenchmarks/lib/src/jni_bench.dart (100%) rename {flutter => packages/flutter}/microbenchmarks/lib/src/memory_bench.dart (100%) rename {flutter => packages/flutter}/microbenchmarks/linux/.gitignore (100%) rename {flutter => packages/flutter}/microbenchmarks/linux/CMakeLists.txt (100%) rename {flutter => packages/flutter}/microbenchmarks/linux/flutter/CMakeLists.txt (100%) rename {flutter => packages/flutter}/microbenchmarks/linux/flutter/generated_plugin_registrant.cc (100%) rename {flutter => packages/flutter}/microbenchmarks/linux/flutter/generated_plugin_registrant.h (100%) rename {flutter => packages/flutter}/microbenchmarks/linux/flutter/generated_plugins.cmake (100%) rename {flutter => packages/flutter}/microbenchmarks/linux/runner/CMakeLists.txt (100%) rename {flutter => packages/flutter}/microbenchmarks/linux/runner/main.cc (100%) rename {flutter => packages/flutter}/microbenchmarks/linux/runner/my_application.cc (100%) rename {flutter => packages/flutter}/microbenchmarks/linux/runner/my_application.h (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/.gitignore (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Flutter/Flutter-Debug.xcconfig (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Flutter/Flutter-Release.xcconfig (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Podfile (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner.xcodeproj/project.pbxproj (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/AppDelegate.swift (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Base.lproj/MainMenu.xib (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Configs/AppInfo.xcconfig (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Configs/Debug.xcconfig (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Configs/Release.xcconfig (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Configs/Warnings.xcconfig (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/DebugProfile.entitlements (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Info.plist (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/MainFlutterWindow.swift (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/Runner/Release.entitlements (100%) rename {flutter => packages/flutter}/microbenchmarks/macos/RunnerTests/RunnerTests.swift (100%) rename {flutter => packages/flutter}/microbenchmarks/pubspec.yaml (100%) rename {flutter => packages/flutter}/microbenchmarks/web/favicon.png (100%) rename {flutter => packages/flutter}/microbenchmarks/web/icons/Icon-192.png (100%) rename {flutter => packages/flutter}/microbenchmarks/web/icons/Icon-512.png (100%) rename {flutter => packages/flutter}/microbenchmarks/web/icons/Icon-maskable-192.png (100%) rename {flutter => packages/flutter}/microbenchmarks/web/icons/Icon-maskable-512.png (100%) rename {flutter => packages/flutter}/microbenchmarks/web/index.html (100%) rename {flutter => packages/flutter}/microbenchmarks/web/manifest.json (100%) rename {flutter/example => packages/flutter/microbenchmarks}/windows/.gitignore (94%) rename {flutter => packages/flutter}/microbenchmarks/windows/CMakeLists.txt (100%) rename {flutter/example => packages/flutter/microbenchmarks}/windows/flutter/CMakeLists.txt (97%) rename {flutter => packages/flutter}/microbenchmarks/windows/flutter/generated_plugin_registrant.cc (100%) rename {flutter => packages/flutter}/microbenchmarks/windows/flutter/generated_plugin_registrant.h (100%) rename {flutter => packages/flutter}/microbenchmarks/windows/flutter/generated_plugins.cmake (100%) rename {flutter/example => packages/flutter/microbenchmarks}/windows/runner/CMakeLists.txt (97%) rename {flutter => packages/flutter}/microbenchmarks/windows/runner/Runner.rc (100%) rename {flutter/example => packages/flutter/microbenchmarks}/windows/runner/flutter_window.cpp (96%) rename {flutter/example => packages/flutter/microbenchmarks}/windows/runner/flutter_window.h (96%) rename {flutter => packages/flutter}/microbenchmarks/windows/runner/main.cpp (100%) rename {flutter/example => packages/flutter/microbenchmarks}/windows/runner/resource.h (96%) rename {flutter => packages/flutter}/microbenchmarks/windows/runner/resources/app_icon.ico (100%) rename {flutter => packages/flutter}/microbenchmarks/windows/runner/runner.exe.manifest (100%) rename {flutter => packages/flutter}/microbenchmarks/windows/runner/utils.cpp (100%) rename {flutter/example => packages/flutter/microbenchmarks}/windows/runner/utils.h (97%) rename {flutter/example => packages/flutter/microbenchmarks}/windows/runner/win32_window.cpp (96%) rename {flutter/example => packages/flutter/microbenchmarks}/windows/runner/win32_window.h (97%) rename {flutter => packages/flutter}/pubspec.yaml (100%) rename {flutter => packages/flutter}/pubspec_overrides.yaml (100%) rename {flutter => packages/flutter}/scripts/generate-cocoa-bindings.sh (100%) rename {flutter => packages/flutter}/scripts/generate-jni-bindings.sh (100%) rename {flutter => packages/flutter}/scripts/generate-native-bindings.ps1 (100%) rename {flutter => packages/flutter}/scripts/update-android.sh (100%) rename {flutter => packages/flutter}/scripts/update-cocoa.sh (100%) rename {flutter => packages/flutter}/scripts/update-js.sh (100%) rename {flutter => packages/flutter}/scripts/update-native.sh (100%) rename {flutter => packages/flutter}/sentry-native/CMakeCache.txt (100%) rename {flutter => packages/flutter}/sentry-native/sentry-native.cmake (100%) rename {flutter => packages/flutter}/test/android_platform_exception_event_processor_test.dart (100%) rename {flutter => packages/flutter}/test/binding.dart (100%) rename {flutter => packages/flutter}/test/event_processor/flutter_enricher_event_processor_test.dart (100%) rename {flutter => packages/flutter}/test/event_processor/flutter_exception_event_processor_test.dart (100%) rename {flutter => packages/flutter}/test/event_processor/platform_exception_event_processor_test.dart (100%) rename {flutter => packages/flutter}/test/event_processor/screenshot_event_processor_test.dart (100%) rename {flutter => packages/flutter}/test/event_processor/url_filter/io_filter_event_processor_test.dart (100%) rename {flutter => packages/flutter}/test/event_processor/url_filter/web_url_filter_event_processor_test.dart (100%) rename {flutter => packages/flutter}/test/event_processor/widget_event_processor_test.dart (100%) rename {flutter => packages/flutter}/test/fake_frame_callback_handler.dart (100%) rename {flutter => packages/flutter}/test/feedback/sentry_feedback_widget_test.dart (100%) rename {flutter => packages/flutter}/test/file_system_transport_test.dart (100%) rename {flutter => packages/flutter}/test/flutter_sentry_attachment_test.dart (100%) rename {flutter => packages/flutter}/test/flutter_version_test.dart (100%) rename {flutter => packages/flutter}/test/frame_tracking/frames_tracking_integration_test.dart (100%) rename {flutter => packages/flutter}/test/frame_tracking/sentry_delayed_frames_tracker_test.dart (100%) rename {flutter => packages/flutter}/test/frame_tracking/span_frame_metrics_collector_test.dart (100%) rename {flutter => packages/flutter}/test/frame_tracking/span_frame_metrics_test.dart (100%) rename {flutter => packages/flutter}/test/initialization_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/connectivity_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/debug_print_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/fixture.dart (100%) rename {flutter => packages/flutter}/test/integrations/flutter_error_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/flutter_framework_feature_flag_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/generic_app_start_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/init_native_sdk_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/load_contexts_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/load_contexts_integrations_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/load_native_debug_images_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/load_release_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/mock_platform_dispatcher.dart (100%) rename {flutter => packages/flutter}/test/integrations/native_app_start_handler_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/native_app_start_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/native_sdk_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/not_initialized_widgets_binding_on_error_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/not_initialized_widgets_binding_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/on_error_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/screenshot_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/web_sdk_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/web_session_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/widgets_binding_integration_test.dart (100%) rename {flutter => packages/flutter}/test/integrations/widgets_flutter_binding_integration_test.dart (100%) rename {flutter => packages/flutter}/test/jvm/jvm_exception_test.dart (100%) rename {flutter => packages/flutter}/test/jvm/jvm_frame_test.dart (100%) rename {flutter => packages/flutter}/test/method_channel_helper_test.dart (100%) rename {flutter => packages/flutter}/test/mocks.dart (100%) rename {flutter => packages/flutter}/test/mocks.mocks.dart (100%) rename {flutter => packages/flutter}/test/native_memory_test.dart (100%) rename {flutter => packages/flutter}/test/native_memory_web_mock.dart (100%) rename {flutter => packages/flutter}/test/native_scope_observer_test.dart (100%) rename {flutter => packages/flutter}/test/navigation/sentry_display_test.dart (100%) rename {flutter => packages/flutter}/test/navigation/sentry_display_widget_test.dart (100%) rename {flutter => packages/flutter}/test/navigation/sentry_navigator_observer_test.dart (100%) rename {flutter => packages/flutter}/test/navigation/sentry_navigator_observer_traces_test.dart (100%) rename {flutter => packages/flutter}/test/navigation/time_to_display_tracker_test.dart (100%) rename {flutter => packages/flutter}/test/navigation/time_to_full_display_tracker_test.dart (100%) rename {flutter => packages/flutter}/test/navigation/time_to_initial_display_tracker_test.dart (100%) rename {flutter => packages/flutter}/test/no_such_method_provider.dart (100%) rename {flutter => packages/flutter}/test/profiling_test.dart (100%) rename {flutter => packages/flutter}/test/replay/android_replay_recorder_web.dart (100%) rename {flutter => packages/flutter}/test/replay/replay_event_processor_test.dart (100%) rename {flutter => packages/flutter}/test/replay/replay_integration_test.dart (100%) rename {flutter => packages/flutter}/test/replay/replay_native_test.dart (100%) rename {flutter => packages/flutter}/test/replay/replay_test_util.dart (100%) rename {flutter => packages/flutter}/test/replay/scheduled_recorder_test.dart (100%) rename {flutter => packages/flutter}/test/replay/scheduler_test.dart (100%) rename {flutter => packages/flutter}/test/screenshot/masking_config_test.dart (100%) rename {flutter => packages/flutter}/test/screenshot/recorder_config_test.dart (100%) rename {flutter => packages/flutter}/test/screenshot/recorder_test.dart (100%) rename {flutter => packages/flutter}/test/screenshot/screenshot_support_test.dart (100%) rename {flutter => packages/flutter}/test/screenshot/sentry_screenshot_quality_test.dart (100%) rename {flutter => packages/flutter}/test/screenshot/sentry_screenshot_widget_test.dart (100%) rename {flutter => packages/flutter}/test/screenshot/sentry_screenshot_widget_test.mocks.dart (100%) rename {flutter => packages/flutter}/test/screenshot/test_widget.dart (100%) rename {flutter => packages/flutter}/test/screenshot/widget_filter_test.dart (100%) rename {flutter => packages/flutter}/test/sentry_asset_bundle_test.dart (100%) rename {flutter => packages/flutter}/test/sentry_flutter_options_test.dart (100%) rename {flutter => packages/flutter}/test/sentry_flutter_test.dart (100%) rename {flutter => packages/flutter}/test/sentry_flutter_util.dart (100%) rename {flutter => packages/flutter}/test/sentry_native/sentry_native_test.dart (100%) rename {flutter => packages/flutter}/test/sentry_native/sentry_native_test_ffi.dart (100%) rename {flutter => packages/flutter}/test/sentry_native/sentry_native_test_web.dart (100%) rename {flutter => packages/flutter}/test/sentry_native_channel_test.dart (100%) rename {flutter => packages/flutter}/test/sentry_widget_test.dart (100%) rename {flutter => packages/flutter}/test/sentry_widgets_binding_mixin_test.dart (100%) rename {flutter => packages/flutter}/test/sentry_widgets_flutter_binding_test.dart (100%) rename {flutter => packages/flutter}/test/user_interaction/sentry_user_interaction_widget_test.dart (100%) rename {flutter => packages/flutter}/test/utils/debouncer_test.dart (100%) rename {flutter => packages/flutter}/test/version_test.dart (100%) rename {flutter => packages/flutter}/test/view_hierarchy/sentry_tree_walker_test.dart (100%) rename {flutter => packages/flutter}/test/view_hierarchy/view_hierarchy_event_processor_test.dart (100%) rename {flutter => packages/flutter}/test/view_hierarchy/view_hierarchy_integration_test.dart (100%) rename {flutter => packages/flutter}/test/web/sentry_js_bundles_test.dart (100%) rename {flutter => packages/flutter}/test/web/sentry_script_loader_test.dart (100%) rename {flutter => packages/flutter}/test/web/sentry_script_loader_tt_custom_test.dart (100%) rename {flutter => packages/flutter}/test/web/sentry_script_loader_tt_forbidden_test.dart (100%) rename {flutter => packages/flutter}/test/web/sentry_web_test.dart (100%) rename {flutter => packages/flutter}/test/web/utils.dart (100%) rename {flutter => packages/flutter}/test/web/web_sentry_js_binding_test.dart (100%) rename {flutter => packages/flutter}/test/web/web_session_handler_test.dart (100%) rename {flutter => packages/flutter}/test/widgets_binding_observer_test.dart (100%) rename {flutter => packages/flutter}/tool/presubmit.sh (100%) rename {flutter => packages/flutter}/windows/.gitignore (94%) rename {flutter => packages/flutter}/windows/CMakeLists.txt (97%) rename {flutter => packages/flutter}/windows/sentry_flutter/sentry_flutter_plugin.h (100%) diff --git a/flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a625..0000000000 --- a/flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/flutter/.editorconfig b/packages/flutter/.editorconfig similarity index 100% rename from flutter/.editorconfig rename to packages/flutter/.editorconfig diff --git a/flutter/.gitignore b/packages/flutter/.gitignore similarity index 100% rename from flutter/.gitignore rename to packages/flutter/.gitignore diff --git a/flutter/.metadata b/packages/flutter/.metadata similarity index 100% rename from flutter/.metadata rename to packages/flutter/.metadata diff --git a/flutter/.pubignore b/packages/flutter/.pubignore similarity index 100% rename from flutter/.pubignore rename to packages/flutter/.pubignore diff --git a/flutter/CHANGELOG.md b/packages/flutter/CHANGELOG.md similarity index 100% rename from flutter/CHANGELOG.md rename to packages/flutter/CHANGELOG.md diff --git a/flutter/LICENSE b/packages/flutter/LICENSE similarity index 100% rename from flutter/LICENSE rename to packages/flutter/LICENSE diff --git a/flutter/README.md b/packages/flutter/README.md similarity index 100% rename from flutter/README.md rename to packages/flutter/README.md diff --git a/flutter/analysis_options.yaml b/packages/flutter/analysis_options.yaml similarity index 100% rename from flutter/analysis_options.yaml rename to packages/flutter/analysis_options.yaml diff --git a/flutter/android/.gitignore b/packages/flutter/android/.gitignore similarity index 100% rename from flutter/android/.gitignore rename to packages/flutter/android/.gitignore diff --git a/flutter/android/build.gradle b/packages/flutter/android/build.gradle similarity index 100% rename from flutter/android/build.gradle rename to packages/flutter/android/build.gradle diff --git a/flutter/android/gradle.properties b/packages/flutter/android/gradle.properties similarity index 100% rename from flutter/android/gradle.properties rename to packages/flutter/android/gradle.properties diff --git a/flutter/android/gradle/wrapper/gradle-wrapper.properties b/packages/flutter/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from flutter/android/gradle/wrapper/gradle-wrapper.properties rename to packages/flutter/android/gradle/wrapper/gradle-wrapper.properties diff --git a/flutter/android/proguard-rules.pro b/packages/flutter/android/proguard-rules.pro similarity index 100% rename from flutter/android/proguard-rules.pro rename to packages/flutter/android/proguard-rules.pro diff --git a/flutter/android/settings.gradle b/packages/flutter/android/settings.gradle similarity index 100% rename from flutter/android/settings.gradle rename to packages/flutter/android/settings.gradle diff --git a/flutter/android/src/main/AndroidManifest.xml b/packages/flutter/android/src/main/AndroidManifest.xml similarity index 100% rename from flutter/android/src/main/AndroidManifest.xml rename to packages/flutter/android/src/main/AndroidManifest.xml diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt similarity index 100% rename from flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt rename to packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt similarity index 100% rename from flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt rename to packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayBreadcrumbConverter.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayBreadcrumbConverter.kt similarity index 100% rename from flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayBreadcrumbConverter.kt rename to packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayBreadcrumbConverter.kt diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayRecorder.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayRecorder.kt similarity index 100% rename from flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayRecorder.kt rename to packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayRecorder.kt diff --git a/flutter/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt b/packages/flutter/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt similarity index 100% rename from flutter/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt rename to packages/flutter/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt diff --git a/flutter/class-diagram.svg b/packages/flutter/class-diagram.svg similarity index 100% rename from flutter/class-diagram.svg rename to packages/flutter/class-diagram.svg diff --git a/flutter/config/detekt-bl.xml b/packages/flutter/config/detekt-bl.xml similarity index 100% rename from flutter/config/detekt-bl.xml rename to packages/flutter/config/detekt-bl.xml diff --git a/flutter/dartdoc_options.yaml b/packages/flutter/dartdoc_options.yaml similarity index 100% rename from flutter/dartdoc_options.yaml rename to packages/flutter/dartdoc_options.yaml diff --git a/flutter/example/.gitignore b/packages/flutter/example/.gitignore similarity index 100% rename from flutter/example/.gitignore rename to packages/flutter/example/.gitignore diff --git a/flutter/example/.metadata b/packages/flutter/example/.metadata similarity index 100% rename from flutter/example/.metadata rename to packages/flutter/example/.metadata diff --git a/flutter/example/README.md b/packages/flutter/example/README.md similarity index 100% rename from flutter/example/README.md rename to packages/flutter/example/README.md diff --git a/flutter/example/analysis_options.yaml b/packages/flutter/example/analysis_options.yaml similarity index 100% rename from flutter/example/analysis_options.yaml rename to packages/flutter/example/analysis_options.yaml diff --git a/flutter/example/android/.gitignore b/packages/flutter/example/android/.gitignore similarity index 100% rename from flutter/example/android/.gitignore rename to packages/flutter/example/android/.gitignore diff --git a/flutter/example/android/app/CMakeLists.txt b/packages/flutter/example/android/app/CMakeLists.txt similarity index 100% rename from flutter/example/android/app/CMakeLists.txt rename to packages/flutter/example/android/app/CMakeLists.txt diff --git a/flutter/example/android/app/build.gradle b/packages/flutter/example/android/app/build.gradle similarity index 100% rename from flutter/example/android/app/build.gradle rename to packages/flutter/example/android/app/build.gradle diff --git a/flutter/example/android/app/proguard-rules.pro b/packages/flutter/example/android/app/proguard-rules.pro similarity index 100% rename from flutter/example/android/app/proguard-rules.pro rename to packages/flutter/example/android/app/proguard-rules.pro diff --git a/flutter/example/android/app/src/main/AndroidManifest.xml b/packages/flutter/example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from flutter/example/android/app/src/main/AndroidManifest.xml rename to packages/flutter/example/android/app/src/main/AndroidManifest.xml diff --git a/flutter/example/android/app/src/main/cpp/native-sample.cpp b/packages/flutter/example/android/app/src/main/cpp/native-sample.cpp similarity index 100% rename from flutter/example/android/app/src/main/cpp/native-sample.cpp rename to packages/flutter/example/android/app/src/main/cpp/native-sample.cpp diff --git a/flutter/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt b/packages/flutter/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt similarity index 100% rename from flutter/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt rename to packages/flutter/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt diff --git a/flutter/example/android/app/src/main/res/drawable/launch_background.xml b/packages/flutter/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from flutter/example/android/app/src/main/res/drawable/launch_background.xml rename to packages/flutter/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/flutter/example/android/app/src/main/res/values/styles.xml b/packages/flutter/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from flutter/example/android/app/src/main/res/values/styles.xml rename to packages/flutter/example/android/app/src/main/res/values/styles.xml diff --git a/flutter/example/android/app/src/main/res/xml/network.xml b/packages/flutter/example/android/app/src/main/res/xml/network.xml similarity index 100% rename from flutter/example/android/app/src/main/res/xml/network.xml rename to packages/flutter/example/android/app/src/main/res/xml/network.xml diff --git a/flutter/example/android/build.gradle b/packages/flutter/example/android/build.gradle similarity index 100% rename from flutter/example/android/build.gradle rename to packages/flutter/example/android/build.gradle diff --git a/flutter/example/android/gradle.properties b/packages/flutter/example/android/gradle.properties similarity index 100% rename from flutter/example/android/gradle.properties rename to packages/flutter/example/android/gradle.properties diff --git a/flutter/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/flutter/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from flutter/example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/flutter/example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/flutter/example/android/sentry.properties b/packages/flutter/example/android/sentry.properties similarity index 100% rename from flutter/example/android/sentry.properties rename to packages/flutter/example/android/sentry.properties diff --git a/flutter/example/android/settings.gradle b/packages/flutter/example/android/settings.gradle similarity index 100% rename from flutter/example/android/settings.gradle rename to packages/flutter/example/android/settings.gradle diff --git a/flutter/example/assets/lorem-ipsum.txt b/packages/flutter/example/assets/lorem-ipsum.txt similarity index 100% rename from flutter/example/assets/lorem-ipsum.txt rename to packages/flutter/example/assets/lorem-ipsum.txt diff --git a/flutter/example/assets/sentry-wordmark.png b/packages/flutter/example/assets/sentry-wordmark.png similarity index 100% rename from flutter/example/assets/sentry-wordmark.png rename to packages/flutter/example/assets/sentry-wordmark.png diff --git a/flutter/example/devtools_options.yaml b/packages/flutter/example/devtools_options.yaml similarity index 100% rename from flutter/example/devtools_options.yaml rename to packages/flutter/example/devtools_options.yaml diff --git a/flutter/example/img/ios_simulator.png b/packages/flutter/example/img/ios_simulator.png similarity index 100% rename from flutter/example/img/ios_simulator.png rename to packages/flutter/example/img/ios_simulator.png diff --git a/flutter/example/img/sentry_dashboard.png b/packages/flutter/example/img/sentry_dashboard.png similarity index 100% rename from flutter/example/img/sentry_dashboard.png rename to packages/flutter/example/img/sentry_dashboard.png diff --git a/flutter/example/integration_test/all.dart b/packages/flutter/example/integration_test/all.dart similarity index 100% rename from flutter/example/integration_test/all.dart rename to packages/flutter/example/integration_test/all.dart diff --git a/flutter/example/integration_test/integration_test.dart b/packages/flutter/example/integration_test/integration_test.dart similarity index 100% rename from flutter/example/integration_test/integration_test.dart rename to packages/flutter/example/integration_test/integration_test.dart diff --git a/flutter/example/integration_test/profiling_test.dart b/packages/flutter/example/integration_test/profiling_test.dart similarity index 100% rename from flutter/example/integration_test/profiling_test.dart rename to packages/flutter/example/integration_test/profiling_test.dart diff --git a/flutter/example/integration_test/replay_test.dart b/packages/flutter/example/integration_test/replay_test.dart similarity index 100% rename from flutter/example/integration_test/replay_test.dart rename to packages/flutter/example/integration_test/replay_test.dart diff --git a/flutter/example/integration_test/sentry_widgets_flutter_binding_test.dart b/packages/flutter/example/integration_test/sentry_widgets_flutter_binding_test.dart similarity index 100% rename from flutter/example/integration_test/sentry_widgets_flutter_binding_test.dart rename to packages/flutter/example/integration_test/sentry_widgets_flutter_binding_test.dart diff --git a/flutter/example/integration_test/test_driver/driver.dart b/packages/flutter/example/integration_test/test_driver/driver.dart similarity index 100% rename from flutter/example/integration_test/test_driver/driver.dart rename to packages/flutter/example/integration_test/test_driver/driver.dart diff --git a/flutter/example/integration_test/utils.dart b/packages/flutter/example/integration_test/utils.dart similarity index 100% rename from flutter/example/integration_test/utils.dart rename to packages/flutter/example/integration_test/utils.dart diff --git a/flutter/example/integration_test/web_sdk_test.dart b/packages/flutter/example/integration_test/web_sdk_test.dart similarity index 100% rename from flutter/example/integration_test/web_sdk_test.dart rename to packages/flutter/example/integration_test/web_sdk_test.dart diff --git a/flutter/example/ios/.gitignore b/packages/flutter/example/ios/.gitignore similarity index 100% rename from flutter/example/ios/.gitignore rename to packages/flutter/example/ios/.gitignore diff --git a/flutter/example/ios/Flutter/AppFrameworkInfo.plist b/packages/flutter/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from flutter/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/flutter/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/flutter/example/ios/Flutter/Debug.xcconfig b/packages/flutter/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from flutter/example/ios/Flutter/Debug.xcconfig rename to packages/flutter/example/ios/Flutter/Debug.xcconfig diff --git a/flutter/example/ios/Flutter/Release.xcconfig b/packages/flutter/example/ios/Flutter/Release.xcconfig similarity index 100% rename from flutter/example/ios/Flutter/Release.xcconfig rename to packages/flutter/example/ios/Flutter/Release.xcconfig diff --git a/flutter/example/ios/Gemfile b/packages/flutter/example/ios/Gemfile similarity index 100% rename from flutter/example/ios/Gemfile rename to packages/flutter/example/ios/Gemfile diff --git a/flutter/example/ios/Gemfile.lock b/packages/flutter/example/ios/Gemfile.lock similarity index 100% rename from flutter/example/ios/Gemfile.lock rename to packages/flutter/example/ios/Gemfile.lock diff --git a/flutter/example/ios/Podfile b/packages/flutter/example/ios/Podfile similarity index 100% rename from flutter/example/ios/Podfile rename to packages/flutter/example/ios/Podfile diff --git a/flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/flutter/example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from flutter/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/flutter/example/ios/Runner.xcodeproj/project.pbxproj diff --git a/flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/flutter/example/ios/Runner/AppDelegate.swift b/packages/flutter/example/ios/Runner/AppDelegate.swift similarity index 100% rename from flutter/example/ios/Runner/AppDelegate.swift rename to packages/flutter/example/ios/Runner/AppDelegate.swift diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/flutter/example/ios/Runner/Base.lproj/Main.storyboard b/packages/flutter/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from flutter/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/flutter/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/flutter/example/ios/Runner/Buggy.h b/packages/flutter/example/ios/Runner/Buggy.h similarity index 100% rename from flutter/example/ios/Runner/Buggy.h rename to packages/flutter/example/ios/Runner/Buggy.h diff --git a/flutter/example/ios/Runner/Buggy.m b/packages/flutter/example/ios/Runner/Buggy.m similarity index 100% rename from flutter/example/ios/Runner/Buggy.m rename to packages/flutter/example/ios/Runner/Buggy.m diff --git a/flutter/example/ios/Runner/Info.plist b/packages/flutter/example/ios/Runner/Info.plist similarity index 100% rename from flutter/example/ios/Runner/Info.plist rename to packages/flutter/example/ios/Runner/Info.plist diff --git a/flutter/example/ios/Runner/Runner-Bridging-Header.h b/packages/flutter/example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from flutter/example/ios/Runner/Runner-Bridging-Header.h rename to packages/flutter/example/ios/Runner/Runner-Bridging-Header.h diff --git a/flutter/example/ios/Runner/Runner.entitlements b/packages/flutter/example/ios/Runner/Runner.entitlements similarity index 100% rename from flutter/example/ios/Runner/Runner.entitlements rename to packages/flutter/example/ios/Runner/Runner.entitlements diff --git a/flutter/example/ios/Runner/RunnerRelease.entitlements b/packages/flutter/example/ios/Runner/RunnerRelease.entitlements similarity index 100% rename from flutter/example/ios/Runner/RunnerRelease.entitlements rename to packages/flutter/example/ios/Runner/RunnerRelease.entitlements diff --git a/flutter/example/ios/RunnerTests/SentryFlutterTests.swift b/packages/flutter/example/ios/RunnerTests/SentryFlutterTests.swift similarity index 100% rename from flutter/example/ios/RunnerTests/SentryFlutterTests.swift rename to packages/flutter/example/ios/RunnerTests/SentryFlutterTests.swift diff --git a/flutter/example/ios/fastlane/Appfile b/packages/flutter/example/ios/fastlane/Appfile similarity index 100% rename from flutter/example/ios/fastlane/Appfile rename to packages/flutter/example/ios/fastlane/Appfile diff --git a/flutter/example/ios/fastlane/Fastfile b/packages/flutter/example/ios/fastlane/Fastfile similarity index 100% rename from flutter/example/ios/fastlane/Fastfile rename to packages/flutter/example/ios/fastlane/Fastfile diff --git a/flutter/example/ios/fastlane/Matchfile b/packages/flutter/example/ios/fastlane/Matchfile similarity index 100% rename from flutter/example/ios/fastlane/Matchfile rename to packages/flutter/example/ios/fastlane/Matchfile diff --git a/flutter/example/lib/auto_close_screen.dart b/packages/flutter/example/lib/auto_close_screen.dart similarity index 100% rename from flutter/example/lib/auto_close_screen.dart rename to packages/flutter/example/lib/auto_close_screen.dart diff --git a/flutter/example/lib/drift/connection/connection.dart b/packages/flutter/example/lib/drift/connection/connection.dart similarity index 100% rename from flutter/example/lib/drift/connection/connection.dart rename to packages/flutter/example/lib/drift/connection/connection.dart diff --git a/flutter/example/lib/drift/connection/native.dart b/packages/flutter/example/lib/drift/connection/native.dart similarity index 100% rename from flutter/example/lib/drift/connection/native.dart rename to packages/flutter/example/lib/drift/connection/native.dart diff --git a/flutter/example/lib/drift/connection/unsupported.dart b/packages/flutter/example/lib/drift/connection/unsupported.dart similarity index 100% rename from flutter/example/lib/drift/connection/unsupported.dart rename to packages/flutter/example/lib/drift/connection/unsupported.dart diff --git a/flutter/example/lib/drift/database.dart b/packages/flutter/example/lib/drift/database.dart similarity index 100% rename from flutter/example/lib/drift/database.dart rename to packages/flutter/example/lib/drift/database.dart diff --git a/flutter/example/lib/drift/database.g.dart b/packages/flutter/example/lib/drift/database.g.dart similarity index 100% rename from flutter/example/lib/drift/database.g.dart rename to packages/flutter/example/lib/drift/database.g.dart diff --git a/flutter/example/lib/isar/user.dart b/packages/flutter/example/lib/isar/user.dart similarity index 100% rename from flutter/example/lib/isar/user.dart rename to packages/flutter/example/lib/isar/user.dart diff --git a/flutter/example/lib/isar/user.g.dart b/packages/flutter/example/lib/isar/user.g.dart similarity index 100% rename from flutter/example/lib/isar/user.g.dart rename to packages/flutter/example/lib/isar/user.g.dart diff --git a/flutter/example/lib/main.dart b/packages/flutter/example/lib/main.dart similarity index 100% rename from flutter/example/lib/main.dart rename to packages/flutter/example/lib/main.dart diff --git a/flutter/example/linux/.gitignore b/packages/flutter/example/linux/.gitignore similarity index 100% rename from flutter/example/linux/.gitignore rename to packages/flutter/example/linux/.gitignore diff --git a/flutter/example/linux/CMakeLists.txt b/packages/flutter/example/linux/CMakeLists.txt similarity index 100% rename from flutter/example/linux/CMakeLists.txt rename to packages/flutter/example/linux/CMakeLists.txt diff --git a/flutter/example/linux/flutter/CMakeLists.txt b/packages/flutter/example/linux/flutter/CMakeLists.txt similarity index 100% rename from flutter/example/linux/flutter/CMakeLists.txt rename to packages/flutter/example/linux/flutter/CMakeLists.txt diff --git a/flutter/example/linux/main.cc b/packages/flutter/example/linux/main.cc similarity index 100% rename from flutter/example/linux/main.cc rename to packages/flutter/example/linux/main.cc diff --git a/flutter/example/linux/my_application.cc b/packages/flutter/example/linux/my_application.cc similarity index 100% rename from flutter/example/linux/my_application.cc rename to packages/flutter/example/linux/my_application.cc diff --git a/flutter/example/linux/my_application.h b/packages/flutter/example/linux/my_application.h similarity index 100% rename from flutter/example/linux/my_application.h rename to packages/flutter/example/linux/my_application.h diff --git a/flutter/example/macos/.gitignore b/packages/flutter/example/macos/.gitignore similarity index 100% rename from flutter/example/macos/.gitignore rename to packages/flutter/example/macos/.gitignore diff --git a/flutter/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/flutter/example/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from flutter/example/macos/Flutter/Flutter-Debug.xcconfig rename to packages/flutter/example/macos/Flutter/Flutter-Debug.xcconfig diff --git a/flutter/example/macos/Flutter/Flutter-Release.xcconfig b/packages/flutter/example/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from flutter/example/macos/Flutter/Flutter-Release.xcconfig rename to packages/flutter/example/macos/Flutter/Flutter-Release.xcconfig diff --git a/flutter/example/macos/Podfile b/packages/flutter/example/macos/Podfile similarity index 100% rename from flutter/example/macos/Podfile rename to packages/flutter/example/macos/Podfile diff --git a/flutter/example/macos/Runner.xcodeproj/project.pbxproj b/packages/flutter/example/macos/Runner.xcodeproj/project.pbxproj similarity index 100% rename from flutter/example/macos/Runner.xcodeproj/project.pbxproj rename to packages/flutter/example/macos/Runner.xcodeproj/project.pbxproj diff --git a/flutter/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from flutter/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/flutter/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/flutter/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/flutter/example/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from flutter/example/macos/Runner.xcworkspace/contents.xcworkspacedata rename to packages/flutter/example/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/flutter/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from flutter/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/flutter/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/flutter/example/macos/Runner/AppDelegate.swift b/packages/flutter/example/macos/Runner/AppDelegate.swift similarity index 100% rename from flutter/example/macos/Runner/AppDelegate.swift rename to packages/flutter/example/macos/Runner/AppDelegate.swift diff --git a/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to packages/flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/flutter/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/flutter/example/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from flutter/example/macos/Runner/Base.lproj/MainMenu.xib rename to packages/flutter/example/macos/Runner/Base.lproj/MainMenu.xib diff --git a/flutter/example/macos/Runner/Buggy.h b/packages/flutter/example/macos/Runner/Buggy.h similarity index 100% rename from flutter/example/macos/Runner/Buggy.h rename to packages/flutter/example/macos/Runner/Buggy.h diff --git a/flutter/example/macos/Runner/Buggy.m b/packages/flutter/example/macos/Runner/Buggy.m similarity index 100% rename from flutter/example/macos/Runner/Buggy.m rename to packages/flutter/example/macos/Runner/Buggy.m diff --git a/flutter/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/flutter/example/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from flutter/example/macos/Runner/Configs/AppInfo.xcconfig rename to packages/flutter/example/macos/Runner/Configs/AppInfo.xcconfig diff --git a/flutter/example/macos/Runner/Configs/Debug.xcconfig b/packages/flutter/example/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from flutter/example/macos/Runner/Configs/Debug.xcconfig rename to packages/flutter/example/macos/Runner/Configs/Debug.xcconfig diff --git a/flutter/example/macos/Runner/Configs/Release.xcconfig b/packages/flutter/example/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from flutter/example/macos/Runner/Configs/Release.xcconfig rename to packages/flutter/example/macos/Runner/Configs/Release.xcconfig diff --git a/flutter/example/macos/Runner/Configs/Warnings.xcconfig b/packages/flutter/example/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from flutter/example/macos/Runner/Configs/Warnings.xcconfig rename to packages/flutter/example/macos/Runner/Configs/Warnings.xcconfig diff --git a/flutter/example/macos/Runner/DebugProfile.entitlements b/packages/flutter/example/macos/Runner/DebugProfile.entitlements similarity index 100% rename from flutter/example/macos/Runner/DebugProfile.entitlements rename to packages/flutter/example/macos/Runner/DebugProfile.entitlements diff --git a/flutter/example/macos/Runner/Info.plist b/packages/flutter/example/macos/Runner/Info.plist similarity index 100% rename from flutter/example/macos/Runner/Info.plist rename to packages/flutter/example/macos/Runner/Info.plist diff --git a/flutter/example/macos/Runner/MainFlutterWindow.swift b/packages/flutter/example/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from flutter/example/macos/Runner/MainFlutterWindow.swift rename to packages/flutter/example/macos/Runner/MainFlutterWindow.swift diff --git a/flutter/example/macos/Runner/Release.entitlements b/packages/flutter/example/macos/Runner/Release.entitlements similarity index 100% rename from flutter/example/macos/Runner/Release.entitlements rename to packages/flutter/example/macos/Runner/Release.entitlements diff --git a/flutter/example/macos/Runner/Runner-Bridging-Header.h b/packages/flutter/example/macos/Runner/Runner-Bridging-Header.h similarity index 100% rename from flutter/example/macos/Runner/Runner-Bridging-Header.h rename to packages/flutter/example/macos/Runner/Runner-Bridging-Header.h diff --git a/flutter/example/pubspec.yaml b/packages/flutter/example/pubspec.yaml similarity index 100% rename from flutter/example/pubspec.yaml rename to packages/flutter/example/pubspec.yaml diff --git a/flutter/example/pubspec_overrides.yaml b/packages/flutter/example/pubspec_overrides.yaml similarity index 100% rename from flutter/example/pubspec_overrides.yaml rename to packages/flutter/example/pubspec_overrides.yaml diff --git a/flutter/example/run.sh b/packages/flutter/example/run.sh similarity index 100% rename from flutter/example/run.sh rename to packages/flutter/example/run.sh diff --git a/flutter/example/web/favicon.png b/packages/flutter/example/web/favicon.png similarity index 100% rename from flutter/example/web/favicon.png rename to packages/flutter/example/web/favicon.png diff --git a/flutter/example/web/icons/Icon-192.png b/packages/flutter/example/web/icons/Icon-192.png similarity index 100% rename from flutter/example/web/icons/Icon-192.png rename to packages/flutter/example/web/icons/Icon-192.png diff --git a/flutter/example/web/icons/Icon-512.png b/packages/flutter/example/web/icons/Icon-512.png similarity index 100% rename from flutter/example/web/icons/Icon-512.png rename to packages/flutter/example/web/icons/Icon-512.png diff --git a/flutter/example/web/index.html b/packages/flutter/example/web/index.html similarity index 100% rename from flutter/example/web/index.html rename to packages/flutter/example/web/index.html diff --git a/flutter/example/web/manifest.json b/packages/flutter/example/web/manifest.json similarity index 100% rename from flutter/example/web/manifest.json rename to packages/flutter/example/web/manifest.json diff --git a/flutter/microbenchmarks/windows/.gitignore b/packages/flutter/example/windows/.gitignore similarity index 100% rename from flutter/microbenchmarks/windows/.gitignore rename to packages/flutter/example/windows/.gitignore diff --git a/flutter/example/windows/CMakeLists.txt b/packages/flutter/example/windows/CMakeLists.txt similarity index 97% rename from flutter/example/windows/CMakeLists.txt rename to packages/flutter/example/windows/CMakeLists.txt index 5a554e25d0..50a839d8b6 100644 --- a/flutter/example/windows/CMakeLists.txt +++ b/packages/flutter/example/windows/CMakeLists.txt @@ -1,108 +1,108 @@ -# Project-level configuration. -cmake_minimum_required(VERSION 3.14) -project(sentry_flutter_example LANGUAGES CXX) - -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "sentry_flutter_example") - -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. -cmake_policy(VERSION 3.14...3.25) - -# Define build configuration option. -get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(IS_MULTICONFIG) - set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" - CACHE STRING "" FORCE) -else() - if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") - endif() -endif() -# Define settings for the Profile build mode. -set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") -set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") -set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") -set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") - -# Use Unicode for all projects. -add_definitions(-DUNICODE -D_UNICODE) - -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_17) - target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") - target_compile_options(${TARGET} PRIVATE /EHsc) - target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") - target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") -endfunction() - -# Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# Application build; see runner/CMakeLists.txt. -add_subdirectory("runner") - - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# Support files are copied into place next to the executable, so that it can -# run in place. This is done instead of making a separate bundle (as on Linux) -# so that building and running from within Visual Studio will work. -set(BUILD_BUNDLE_DIR "$") -# Make the "install" step default, as it's required to run. -set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -if(PLUGIN_BUNDLED_LIBRARIES) - install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() - -# Copy the native assets provided by the build.dart from all packages. -set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") -install(DIRECTORY "${NATIVE_ASSETS_DIR}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - CONFIGURATIONS Profile;Release - COMPONENT Runtime) +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(sentry_flutter_example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "sentry_flutter_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/flutter/example/windows/flutter/.gitignore b/packages/flutter/example/windows/flutter/.gitignore similarity index 100% rename from flutter/example/windows/flutter/.gitignore rename to packages/flutter/example/windows/flutter/.gitignore diff --git a/flutter/microbenchmarks/windows/flutter/CMakeLists.txt b/packages/flutter/example/windows/flutter/CMakeLists.txt similarity index 100% rename from flutter/microbenchmarks/windows/flutter/CMakeLists.txt rename to packages/flutter/example/windows/flutter/CMakeLists.txt diff --git a/flutter/microbenchmarks/windows/runner/CMakeLists.txt b/packages/flutter/example/windows/runner/CMakeLists.txt similarity index 100% rename from flutter/microbenchmarks/windows/runner/CMakeLists.txt rename to packages/flutter/example/windows/runner/CMakeLists.txt diff --git a/flutter/example/windows/runner/Runner.rc b/packages/flutter/example/windows/runner/Runner.rc similarity index 96% rename from flutter/example/windows/runner/Runner.rc rename to packages/flutter/example/windows/runner/Runner.rc index 90e64b7b0f..0a3a3d5b45 100644 --- a/flutter/example/windows/runner/Runner.rc +++ b/packages/flutter/example/windows/runner/Runner.rc @@ -1,121 +1,121 @@ -// Microsoft Visual C++ generated resource script. -// -#pragma code_page(65001) -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_APP_ICON ICON "resources\\app_icon.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) -#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD -#else -#define VERSION_AS_NUMBER 1,0,0,0 -#endif - -#if defined(FLUTTER_VERSION) -#define VERSION_AS_STRING FLUTTER_VERSION -#else -#define VERSION_AS_STRING "1.0.0" -#endif - -VS_VERSION_INFO VERSIONINFO - FILEVERSION VERSION_AS_NUMBER - PRODUCTVERSION VERSION_AS_NUMBER - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_APP - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904e4" - BEGIN - VALUE "CompanyName", "com.example" "\0" - VALUE "FileDescription", "sentry_flutter_example" "\0" - VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "sentry_flutter_example" "\0" - VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" - VALUE "OriginalFilename", "sentry_flutter_example.exe" "\0" - VALUE "ProductName", "sentry_flutter_example" "\0" - VALUE "ProductVersion", VERSION_AS_STRING "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 - END -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "sentry_flutter_example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "sentry_flutter_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "sentry_flutter_example.exe" "\0" + VALUE "ProductName", "sentry_flutter_example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/flutter/microbenchmarks/windows/runner/flutter_window.cpp b/packages/flutter/example/windows/runner/flutter_window.cpp similarity index 100% rename from flutter/microbenchmarks/windows/runner/flutter_window.cpp rename to packages/flutter/example/windows/runner/flutter_window.cpp diff --git a/flutter/microbenchmarks/windows/runner/flutter_window.h b/packages/flutter/example/windows/runner/flutter_window.h similarity index 100% rename from flutter/microbenchmarks/windows/runner/flutter_window.h rename to packages/flutter/example/windows/runner/flutter_window.h diff --git a/flutter/example/windows/runner/main.cpp b/packages/flutter/example/windows/runner/main.cpp similarity index 96% rename from flutter/example/windows/runner/main.cpp rename to packages/flutter/example/windows/runner/main.cpp index 08f9f5fc8f..0a6001c773 100644 --- a/flutter/example/windows/runner/main.cpp +++ b/packages/flutter/example/windows/runner/main.cpp @@ -1,43 +1,43 @@ -#include -#include -#include - -#include "flutter_window.h" -#include "utils.h" - -int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, - _In_ wchar_t *command_line, _In_ int show_command) { - // Attach to console when present (e.g., 'flutter run') or create a - // new console when running with a debugger. - // if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { - CreateAndAttachConsole(); - // } - - // Initialize COM, so that it is available for use in the library and/or - // plugins. - ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - - flutter::DartProject project(L"data"); - - std::vector command_line_arguments = - GetCommandLineArguments(); - - project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); - - FlutterWindow window(project); - Win32Window::Point origin(10, 10); - Win32Window::Size size(1280, 720); - if (!window.Create(L"sentry_flutter_example", origin, size)) { - return EXIT_FAILURE; - } - window.SetQuitOnClose(true); - - ::MSG msg; - while (::GetMessage(&msg, nullptr, 0, 0)) { - ::TranslateMessage(&msg); - ::DispatchMessage(&msg); - } - - ::CoUninitialize(); - return EXIT_SUCCESS; -} +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + // if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + // } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"sentry_flutter_example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/flutter/microbenchmarks/windows/runner/resource.h b/packages/flutter/example/windows/runner/resource.h similarity index 100% rename from flutter/microbenchmarks/windows/runner/resource.h rename to packages/flutter/example/windows/runner/resource.h diff --git a/flutter/example/windows/runner/resources/app_icon.ico b/packages/flutter/example/windows/runner/resources/app_icon.ico similarity index 100% rename from flutter/example/windows/runner/resources/app_icon.ico rename to packages/flutter/example/windows/runner/resources/app_icon.ico diff --git a/flutter/example/windows/runner/runner.exe.manifest b/packages/flutter/example/windows/runner/runner.exe.manifest similarity index 97% rename from flutter/example/windows/runner/runner.exe.manifest rename to packages/flutter/example/windows/runner/runner.exe.manifest index 157e871fe8..a42ea7687c 100644 --- a/flutter/example/windows/runner/runner.exe.manifest +++ b/packages/flutter/example/windows/runner/runner.exe.manifest @@ -1,20 +1,20 @@ - - - - - PerMonitorV2 - - - - - - - - - - - - - - - + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/flutter/example/windows/runner/utils.cpp b/packages/flutter/example/windows/runner/utils.cpp similarity index 96% rename from flutter/example/windows/runner/utils.cpp rename to packages/flutter/example/windows/runner/utils.cpp index fc55c573b5..b2b08734db 100644 --- a/flutter/example/windows/runner/utils.cpp +++ b/packages/flutter/example/windows/runner/utils.cpp @@ -1,65 +1,65 @@ -#include "utils.h" - -#include -#include -#include -#include - -#include - -void CreateAndAttachConsole() { - if (::AllocConsole()) { - FILE *unused; - if (freopen_s(&unused, "CONOUT$", "w", stdout)) { - _dup2(_fileno(stdout), 1); - } - if (freopen_s(&unused, "CONOUT$", "w", stderr)) { - _dup2(_fileno(stdout), 2); - } - std::ios::sync_with_stdio(); - FlutterDesktopResyncOutputStreams(); - } -} - -std::vector GetCommandLineArguments() { - // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. - int argc; - wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - if (argv == nullptr) { - return std::vector(); - } - - std::vector command_line_arguments; - - // Skip the first argument as it's the binary name. - for (int i = 1; i < argc; i++) { - command_line_arguments.push_back(Utf8FromUtf16(argv[i])); - } - - ::LocalFree(argv); - - return command_line_arguments; -} - -std::string Utf8FromUtf16(const wchar_t* utf16_string) { - if (utf16_string == nullptr) { - return std::string(); - } - int target_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, nullptr, 0, nullptr, nullptr) - -1; // remove the trailing null character - int input_length = (int)wcslen(utf16_string); - std::string utf8_string; - if (target_length <= 0 || target_length > utf8_string.max_size()) { - return utf8_string; - } - utf8_string.resize(target_length); - int converted_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - input_length, utf8_string.data(), target_length, nullptr, nullptr); - if (converted_length == 0) { - return std::string(); - } - return utf8_string; -} +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/flutter/microbenchmarks/windows/runner/utils.h b/packages/flutter/example/windows/runner/utils.h similarity index 100% rename from flutter/microbenchmarks/windows/runner/utils.h rename to packages/flutter/example/windows/runner/utils.h diff --git a/flutter/microbenchmarks/windows/runner/win32_window.cpp b/packages/flutter/example/windows/runner/win32_window.cpp similarity index 100% rename from flutter/microbenchmarks/windows/runner/win32_window.cpp rename to packages/flutter/example/windows/runner/win32_window.cpp diff --git a/flutter/microbenchmarks/windows/runner/win32_window.h b/packages/flutter/example/windows/runner/win32_window.h similarity index 100% rename from flutter/microbenchmarks/windows/runner/win32_window.h rename to packages/flutter/example/windows/runner/win32_window.h diff --git a/flutter/ffi-cocoa.yaml b/packages/flutter/ffi-cocoa.yaml similarity index 100% rename from flutter/ffi-cocoa.yaml rename to packages/flutter/ffi-cocoa.yaml diff --git a/flutter/ffi-jni.yaml b/packages/flutter/ffi-jni.yaml similarity index 100% rename from flutter/ffi-jni.yaml rename to packages/flutter/ffi-jni.yaml diff --git a/flutter/ffi-native.yaml b/packages/flutter/ffi-native.yaml similarity index 100% rename from flutter/ffi-native.yaml rename to packages/flutter/ffi-native.yaml diff --git a/flutter/ios/.gitignore b/packages/flutter/ios/.gitignore similarity index 100% rename from flutter/ios/.gitignore rename to packages/flutter/ios/.gitignore diff --git a/flutter/ios/sentry_flutter.podspec b/packages/flutter/ios/sentry_flutter.podspec similarity index 100% rename from flutter/ios/sentry_flutter.podspec rename to packages/flutter/ios/sentry_flutter.podspec diff --git a/flutter/ios/sentry_flutter/Package.swift b/packages/flutter/ios/sentry_flutter/Package.swift similarity index 100% rename from flutter/ios/sentry_flutter/Package.swift rename to packages/flutter/ios/sentry_flutter/Package.swift diff --git a/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutter.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutter.swift similarity index 100% rename from flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutter.swift rename to packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutter.swift diff --git a/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift similarity index 100% rename from flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift rename to packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift diff --git a/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayBreadcrumbConverter.m b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayBreadcrumbConverter.m similarity index 100% rename from flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayBreadcrumbConverter.m rename to packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayBreadcrumbConverter.m diff --git a/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayScreenshotProvider.m b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayScreenshotProvider.m similarity index 100% rename from flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayScreenshotProvider.m rename to packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayScreenshotProvider.m diff --git a/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayBreadcrumbConverter.h b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayBreadcrumbConverter.h similarity index 100% rename from flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayBreadcrumbConverter.h rename to packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayBreadcrumbConverter.h diff --git a/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayScreenshotProvider.h b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayScreenshotProvider.h similarity index 100% rename from flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayScreenshotProvider.h rename to packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayScreenshotProvider.h diff --git a/flutter/lib/sentry_flutter.dart b/packages/flutter/lib/sentry_flutter.dart similarity index 100% rename from flutter/lib/sentry_flutter.dart rename to packages/flutter/lib/sentry_flutter.dart diff --git a/flutter/lib/sentry_flutter_web.dart b/packages/flutter/lib/sentry_flutter_web.dart similarity index 100% rename from flutter/lib/sentry_flutter_web.dart rename to packages/flutter/lib/sentry_flutter_web.dart diff --git a/flutter/lib/src/binding_wrapper.dart b/packages/flutter/lib/src/binding_wrapper.dart similarity index 100% rename from flutter/lib/src/binding_wrapper.dart rename to packages/flutter/lib/src/binding_wrapper.dart diff --git a/flutter/lib/src/event_processor/android_platform_exception_event_processor.dart b/packages/flutter/lib/src/event_processor/android_platform_exception_event_processor.dart similarity index 100% rename from flutter/lib/src/event_processor/android_platform_exception_event_processor.dart rename to packages/flutter/lib/src/event_processor/android_platform_exception_event_processor.dart diff --git a/flutter/lib/src/event_processor/flutter_enricher_event_processor.dart b/packages/flutter/lib/src/event_processor/flutter_enricher_event_processor.dart similarity index 100% rename from flutter/lib/src/event_processor/flutter_enricher_event_processor.dart rename to packages/flutter/lib/src/event_processor/flutter_enricher_event_processor.dart diff --git a/flutter/lib/src/event_processor/flutter_exception_event_processor.dart b/packages/flutter/lib/src/event_processor/flutter_exception_event_processor.dart similarity index 100% rename from flutter/lib/src/event_processor/flutter_exception_event_processor.dart rename to packages/flutter/lib/src/event_processor/flutter_exception_event_processor.dart diff --git a/flutter/lib/src/event_processor/platform_exception_event_processor.dart b/packages/flutter/lib/src/event_processor/platform_exception_event_processor.dart similarity index 100% rename from flutter/lib/src/event_processor/platform_exception_event_processor.dart rename to packages/flutter/lib/src/event_processor/platform_exception_event_processor.dart diff --git a/flutter/lib/src/event_processor/replay_event_processor.dart b/packages/flutter/lib/src/event_processor/replay_event_processor.dart similarity index 100% rename from flutter/lib/src/event_processor/replay_event_processor.dart rename to packages/flutter/lib/src/event_processor/replay_event_processor.dart diff --git a/flutter/lib/src/event_processor/screenshot_event_processor.dart b/packages/flutter/lib/src/event_processor/screenshot_event_processor.dart similarity index 100% rename from flutter/lib/src/event_processor/screenshot_event_processor.dart rename to packages/flutter/lib/src/event_processor/screenshot_event_processor.dart diff --git a/flutter/lib/src/event_processor/url_filter/io_url_filter_event_processor.dart b/packages/flutter/lib/src/event_processor/url_filter/io_url_filter_event_processor.dart similarity index 100% rename from flutter/lib/src/event_processor/url_filter/io_url_filter_event_processor.dart rename to packages/flutter/lib/src/event_processor/url_filter/io_url_filter_event_processor.dart diff --git a/flutter/lib/src/event_processor/url_filter/url_filter_event_processor.dart b/packages/flutter/lib/src/event_processor/url_filter/url_filter_event_processor.dart similarity index 100% rename from flutter/lib/src/event_processor/url_filter/url_filter_event_processor.dart rename to packages/flutter/lib/src/event_processor/url_filter/url_filter_event_processor.dart diff --git a/flutter/lib/src/event_processor/url_filter/web_url_filter_event_processor.dart b/packages/flutter/lib/src/event_processor/url_filter/web_url_filter_event_processor.dart similarity index 100% rename from flutter/lib/src/event_processor/url_filter/web_url_filter_event_processor.dart rename to packages/flutter/lib/src/event_processor/url_filter/web_url_filter_event_processor.dart diff --git a/flutter/lib/src/event_processor/widget_event_processor.dart b/packages/flutter/lib/src/event_processor/widget_event_processor.dart similarity index 100% rename from flutter/lib/src/event_processor/widget_event_processor.dart rename to packages/flutter/lib/src/event_processor/widget_event_processor.dart diff --git a/flutter/lib/src/feedback/sentry_feedback_options.dart b/packages/flutter/lib/src/feedback/sentry_feedback_options.dart similarity index 100% rename from flutter/lib/src/feedback/sentry_feedback_options.dart rename to packages/flutter/lib/src/feedback/sentry_feedback_options.dart diff --git a/flutter/lib/src/feedback/sentry_feedback_widget.dart b/packages/flutter/lib/src/feedback/sentry_feedback_widget.dart similarity index 100% rename from flutter/lib/src/feedback/sentry_feedback_widget.dart rename to packages/flutter/lib/src/feedback/sentry_feedback_widget.dart diff --git a/flutter/lib/src/feedback/sentry_logo.dart b/packages/flutter/lib/src/feedback/sentry_logo.dart similarity index 100% rename from flutter/lib/src/feedback/sentry_logo.dart rename to packages/flutter/lib/src/feedback/sentry_logo.dart diff --git a/flutter/lib/src/file_system_transport.dart b/packages/flutter/lib/src/file_system_transport.dart similarity index 100% rename from flutter/lib/src/file_system_transport.dart rename to packages/flutter/lib/src/file_system_transport.dart diff --git a/flutter/lib/src/flutter_exception_type_identifier.dart b/packages/flutter/lib/src/flutter_exception_type_identifier.dart similarity index 100% rename from flutter/lib/src/flutter_exception_type_identifier.dart rename to packages/flutter/lib/src/flutter_exception_type_identifier.dart diff --git a/flutter/lib/src/flutter_sentry_attachment.dart b/packages/flutter/lib/src/flutter_sentry_attachment.dart similarity index 100% rename from flutter/lib/src/flutter_sentry_attachment.dart rename to packages/flutter/lib/src/flutter_sentry_attachment.dart diff --git a/flutter/lib/src/frame_callback_handler.dart b/packages/flutter/lib/src/frame_callback_handler.dart similarity index 100% rename from flutter/lib/src/frame_callback_handler.dart rename to packages/flutter/lib/src/frame_callback_handler.dart diff --git a/flutter/lib/src/frames_tracking/sentry_delayed_frames_tracker.dart b/packages/flutter/lib/src/frames_tracking/sentry_delayed_frames_tracker.dart similarity index 100% rename from flutter/lib/src/frames_tracking/sentry_delayed_frames_tracker.dart rename to packages/flutter/lib/src/frames_tracking/sentry_delayed_frames_tracker.dart diff --git a/flutter/lib/src/frames_tracking/span_frame_metrics_collector.dart b/packages/flutter/lib/src/frames_tracking/span_frame_metrics_collector.dart similarity index 100% rename from flutter/lib/src/frames_tracking/span_frame_metrics_collector.dart rename to packages/flutter/lib/src/frames_tracking/span_frame_metrics_collector.dart diff --git a/flutter/lib/src/integrations/connectivity/connectivity_integration.dart b/packages/flutter/lib/src/integrations/connectivity/connectivity_integration.dart similarity index 100% rename from flutter/lib/src/integrations/connectivity/connectivity_integration.dart rename to packages/flutter/lib/src/integrations/connectivity/connectivity_integration.dart diff --git a/flutter/lib/src/integrations/connectivity/connectivity_provider.dart b/packages/flutter/lib/src/integrations/connectivity/connectivity_provider.dart similarity index 100% rename from flutter/lib/src/integrations/connectivity/connectivity_provider.dart rename to packages/flutter/lib/src/integrations/connectivity/connectivity_provider.dart diff --git a/flutter/lib/src/integrations/connectivity/noop_connectivity_provider.dart b/packages/flutter/lib/src/integrations/connectivity/noop_connectivity_provider.dart similarity index 100% rename from flutter/lib/src/integrations/connectivity/noop_connectivity_provider.dart rename to packages/flutter/lib/src/integrations/connectivity/noop_connectivity_provider.dart diff --git a/flutter/lib/src/integrations/connectivity/web_connectivity_provider.dart b/packages/flutter/lib/src/integrations/connectivity/web_connectivity_provider.dart similarity index 100% rename from flutter/lib/src/integrations/connectivity/web_connectivity_provider.dart rename to packages/flutter/lib/src/integrations/connectivity/web_connectivity_provider.dart diff --git a/flutter/lib/src/integrations/debug_print_integration.dart b/packages/flutter/lib/src/integrations/debug_print_integration.dart similarity index 100% rename from flutter/lib/src/integrations/debug_print_integration.dart rename to packages/flutter/lib/src/integrations/debug_print_integration.dart diff --git a/flutter/lib/src/integrations/flutter_error_integration.dart b/packages/flutter/lib/src/integrations/flutter_error_integration.dart similarity index 100% rename from flutter/lib/src/integrations/flutter_error_integration.dart rename to packages/flutter/lib/src/integrations/flutter_error_integration.dart diff --git a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart b/packages/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart similarity index 100% rename from flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart rename to packages/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart diff --git a/flutter/lib/src/integrations/frames_tracking_integration.dart b/packages/flutter/lib/src/integrations/frames_tracking_integration.dart similarity index 100% rename from flutter/lib/src/integrations/frames_tracking_integration.dart rename to packages/flutter/lib/src/integrations/frames_tracking_integration.dart diff --git a/flutter/lib/src/integrations/generic_app_start_integration.dart b/packages/flutter/lib/src/integrations/generic_app_start_integration.dart similarity index 100% rename from flutter/lib/src/integrations/generic_app_start_integration.dart rename to packages/flutter/lib/src/integrations/generic_app_start_integration.dart diff --git a/flutter/lib/src/integrations/integrations.dart b/packages/flutter/lib/src/integrations/integrations.dart similarity index 100% rename from flutter/lib/src/integrations/integrations.dart rename to packages/flutter/lib/src/integrations/integrations.dart diff --git a/flutter/lib/src/integrations/load_contexts_integration.dart b/packages/flutter/lib/src/integrations/load_contexts_integration.dart similarity index 100% rename from flutter/lib/src/integrations/load_contexts_integration.dart rename to packages/flutter/lib/src/integrations/load_contexts_integration.dart diff --git a/flutter/lib/src/integrations/load_debug_images_integration.dart b/packages/flutter/lib/src/integrations/load_debug_images_integration.dart similarity index 100% rename from flutter/lib/src/integrations/load_debug_images_integration.dart rename to packages/flutter/lib/src/integrations/load_debug_images_integration.dart diff --git a/flutter/lib/src/integrations/load_release_integration.dart b/packages/flutter/lib/src/integrations/load_release_integration.dart similarity index 100% rename from flutter/lib/src/integrations/load_release_integration.dart rename to packages/flutter/lib/src/integrations/load_release_integration.dart diff --git a/flutter/lib/src/integrations/native_app_start_handler.dart b/packages/flutter/lib/src/integrations/native_app_start_handler.dart similarity index 100% rename from flutter/lib/src/integrations/native_app_start_handler.dart rename to packages/flutter/lib/src/integrations/native_app_start_handler.dart diff --git a/flutter/lib/src/integrations/native_app_start_integration.dart b/packages/flutter/lib/src/integrations/native_app_start_integration.dart similarity index 100% rename from flutter/lib/src/integrations/native_app_start_integration.dart rename to packages/flutter/lib/src/integrations/native_app_start_integration.dart diff --git a/flutter/lib/src/integrations/native_load_debug_images_integration.dart b/packages/flutter/lib/src/integrations/native_load_debug_images_integration.dart similarity index 100% rename from flutter/lib/src/integrations/native_load_debug_images_integration.dart rename to packages/flutter/lib/src/integrations/native_load_debug_images_integration.dart diff --git a/flutter/lib/src/integrations/native_sdk_integration.dart b/packages/flutter/lib/src/integrations/native_sdk_integration.dart similarity index 100% rename from flutter/lib/src/integrations/native_sdk_integration.dart rename to packages/flutter/lib/src/integrations/native_sdk_integration.dart diff --git a/flutter/lib/src/integrations/on_error_integration.dart b/packages/flutter/lib/src/integrations/on_error_integration.dart similarity index 100% rename from flutter/lib/src/integrations/on_error_integration.dart rename to packages/flutter/lib/src/integrations/on_error_integration.dart diff --git a/flutter/lib/src/integrations/screenshot_integration.dart b/packages/flutter/lib/src/integrations/screenshot_integration.dart similarity index 100% rename from flutter/lib/src/integrations/screenshot_integration.dart rename to packages/flutter/lib/src/integrations/screenshot_integration.dart diff --git a/flutter/lib/src/integrations/sdk_integration.dart b/packages/flutter/lib/src/integrations/sdk_integration.dart similarity index 100% rename from flutter/lib/src/integrations/sdk_integration.dart rename to packages/flutter/lib/src/integrations/sdk_integration.dart diff --git a/flutter/lib/src/integrations/web_load_debug_images_integration.dart b/packages/flutter/lib/src/integrations/web_load_debug_images_integration.dart similarity index 100% rename from flutter/lib/src/integrations/web_load_debug_images_integration.dart rename to packages/flutter/lib/src/integrations/web_load_debug_images_integration.dart diff --git a/flutter/lib/src/integrations/web_sdk_integration.dart b/packages/flutter/lib/src/integrations/web_sdk_integration.dart similarity index 100% rename from flutter/lib/src/integrations/web_sdk_integration.dart rename to packages/flutter/lib/src/integrations/web_sdk_integration.dart diff --git a/flutter/lib/src/integrations/web_session_integration.dart b/packages/flutter/lib/src/integrations/web_session_integration.dart similarity index 100% rename from flutter/lib/src/integrations/web_session_integration.dart rename to packages/flutter/lib/src/integrations/web_session_integration.dart diff --git a/flutter/lib/src/integrations/widgets_binding_integration.dart b/packages/flutter/lib/src/integrations/widgets_binding_integration.dart similarity index 100% rename from flutter/lib/src/integrations/widgets_binding_integration.dart rename to packages/flutter/lib/src/integrations/widgets_binding_integration.dart diff --git a/flutter/lib/src/integrations/widgets_flutter_binding_integration.dart b/packages/flutter/lib/src/integrations/widgets_flutter_binding_integration.dart similarity index 100% rename from flutter/lib/src/integrations/widgets_flutter_binding_integration.dart rename to packages/flutter/lib/src/integrations/widgets_flutter_binding_integration.dart diff --git a/flutter/lib/src/jvm/jvm_exception.dart b/packages/flutter/lib/src/jvm/jvm_exception.dart similarity index 100% rename from flutter/lib/src/jvm/jvm_exception.dart rename to packages/flutter/lib/src/jvm/jvm_exception.dart diff --git a/flutter/lib/src/jvm/jvm_frame.dart b/packages/flutter/lib/src/jvm/jvm_frame.dart similarity index 100% rename from flutter/lib/src/jvm/jvm_frame.dart rename to packages/flutter/lib/src/jvm/jvm_frame.dart diff --git a/flutter/lib/src/native/c/binding.dart b/packages/flutter/lib/src/native/c/binding.dart similarity index 100% rename from flutter/lib/src/native/c/binding.dart rename to packages/flutter/lib/src/native/c/binding.dart diff --git a/flutter/lib/src/native/c/sentry_native.dart b/packages/flutter/lib/src/native/c/sentry_native.dart similarity index 100% rename from flutter/lib/src/native/c/sentry_native.dart rename to packages/flutter/lib/src/native/c/sentry_native.dart diff --git a/flutter/lib/src/native/c/utils.dart b/packages/flutter/lib/src/native/c/utils.dart similarity index 100% rename from flutter/lib/src/native/c/utils.dart rename to packages/flutter/lib/src/native/c/utils.dart diff --git a/flutter/lib/src/native/cocoa/binding.dart b/packages/flutter/lib/src/native/cocoa/binding.dart similarity index 100% rename from flutter/lib/src/native/cocoa/binding.dart rename to packages/flutter/lib/src/native/cocoa/binding.dart diff --git a/flutter/lib/src/native/cocoa/cocoa_replay_recorder.dart b/packages/flutter/lib/src/native/cocoa/cocoa_replay_recorder.dart similarity index 100% rename from flutter/lib/src/native/cocoa/cocoa_replay_recorder.dart rename to packages/flutter/lib/src/native/cocoa/cocoa_replay_recorder.dart diff --git a/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart similarity index 100% rename from flutter/lib/src/native/cocoa/sentry_native_cocoa.dart rename to packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart diff --git a/flutter/lib/src/native/factory.dart b/packages/flutter/lib/src/native/factory.dart similarity index 100% rename from flutter/lib/src/native/factory.dart rename to packages/flutter/lib/src/native/factory.dart diff --git a/flutter/lib/src/native/factory_real.dart b/packages/flutter/lib/src/native/factory_real.dart similarity index 100% rename from flutter/lib/src/native/factory_real.dart rename to packages/flutter/lib/src/native/factory_real.dart diff --git a/flutter/lib/src/native/factory_web.dart b/packages/flutter/lib/src/native/factory_web.dart similarity index 100% rename from flutter/lib/src/native/factory_web.dart rename to packages/flutter/lib/src/native/factory_web.dart diff --git a/flutter/lib/src/native/java/android_replay_recorder.dart b/packages/flutter/lib/src/native/java/android_replay_recorder.dart similarity index 100% rename from flutter/lib/src/native/java/android_replay_recorder.dart rename to packages/flutter/lib/src/native/java/android_replay_recorder.dart diff --git a/flutter/lib/src/native/java/binding.dart b/packages/flutter/lib/src/native/java/binding.dart similarity index 100% rename from flutter/lib/src/native/java/binding.dart rename to packages/flutter/lib/src/native/java/binding.dart diff --git a/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart similarity index 100% rename from flutter/lib/src/native/java/sentry_native_java.dart rename to packages/flutter/lib/src/native/java/sentry_native_java.dart diff --git a/flutter/lib/src/native/method_channel_helper.dart b/packages/flutter/lib/src/native/method_channel_helper.dart similarity index 100% rename from flutter/lib/src/native/method_channel_helper.dart rename to packages/flutter/lib/src/native/method_channel_helper.dart diff --git a/flutter/lib/src/native/native_app_start.dart b/packages/flutter/lib/src/native/native_app_start.dart similarity index 100% rename from flutter/lib/src/native/native_app_start.dart rename to packages/flutter/lib/src/native/native_app_start.dart diff --git a/flutter/lib/src/native/native_memory.dart b/packages/flutter/lib/src/native/native_memory.dart similarity index 100% rename from flutter/lib/src/native/native_memory.dart rename to packages/flutter/lib/src/native/native_memory.dart diff --git a/flutter/lib/src/native/native_scope_observer.dart b/packages/flutter/lib/src/native/native_scope_observer.dart similarity index 100% rename from flutter/lib/src/native/native_scope_observer.dart rename to packages/flutter/lib/src/native/native_scope_observer.dart diff --git a/flutter/lib/src/native/sentry_native_binding.dart b/packages/flutter/lib/src/native/sentry_native_binding.dart similarity index 100% rename from flutter/lib/src/native/sentry_native_binding.dart rename to packages/flutter/lib/src/native/sentry_native_binding.dart diff --git a/flutter/lib/src/native/sentry_native_channel.dart b/packages/flutter/lib/src/native/sentry_native_channel.dart similarity index 100% rename from flutter/lib/src/native/sentry_native_channel.dart rename to packages/flutter/lib/src/native/sentry_native_channel.dart diff --git a/flutter/lib/src/native/sentry_native_invoker.dart b/packages/flutter/lib/src/native/sentry_native_invoker.dart similarity index 100% rename from flutter/lib/src/native/sentry_native_invoker.dart rename to packages/flutter/lib/src/native/sentry_native_invoker.dart diff --git a/flutter/lib/src/native/sentry_safe_method_channel.dart b/packages/flutter/lib/src/native/sentry_safe_method_channel.dart similarity index 100% rename from flutter/lib/src/native/sentry_safe_method_channel.dart rename to packages/flutter/lib/src/native/sentry_safe_method_channel.dart diff --git a/flutter/lib/src/navigation/sentry_display.dart b/packages/flutter/lib/src/navigation/sentry_display.dart similarity index 100% rename from flutter/lib/src/navigation/sentry_display.dart rename to packages/flutter/lib/src/navigation/sentry_display.dart diff --git a/flutter/lib/src/navigation/sentry_display_widget.dart b/packages/flutter/lib/src/navigation/sentry_display_widget.dart similarity index 100% rename from flutter/lib/src/navigation/sentry_display_widget.dart rename to packages/flutter/lib/src/navigation/sentry_display_widget.dart diff --git a/flutter/lib/src/navigation/sentry_navigator_observer.dart b/packages/flutter/lib/src/navigation/sentry_navigator_observer.dart similarity index 100% rename from flutter/lib/src/navigation/sentry_navigator_observer.dart rename to packages/flutter/lib/src/navigation/sentry_navigator_observer.dart diff --git a/flutter/lib/src/navigation/time_to_display_tracker.dart b/packages/flutter/lib/src/navigation/time_to_display_tracker.dart similarity index 100% rename from flutter/lib/src/navigation/time_to_display_tracker.dart rename to packages/flutter/lib/src/navigation/time_to_display_tracker.dart diff --git a/flutter/lib/src/navigation/time_to_full_display_tracker.dart b/packages/flutter/lib/src/navigation/time_to_full_display_tracker.dart similarity index 100% rename from flutter/lib/src/navigation/time_to_full_display_tracker.dart rename to packages/flutter/lib/src/navigation/time_to_full_display_tracker.dart diff --git a/flutter/lib/src/navigation/time_to_initial_display_tracker.dart b/packages/flutter/lib/src/navigation/time_to_initial_display_tracker.dart similarity index 100% rename from flutter/lib/src/navigation/time_to_initial_display_tracker.dart rename to packages/flutter/lib/src/navigation/time_to_initial_display_tracker.dart diff --git a/flutter/lib/src/profiling.dart b/packages/flutter/lib/src/profiling.dart similarity index 100% rename from flutter/lib/src/profiling.dart rename to packages/flutter/lib/src/profiling.dart diff --git a/flutter/lib/src/renderer/io_renderer.dart b/packages/flutter/lib/src/renderer/io_renderer.dart similarity index 100% rename from flutter/lib/src/renderer/io_renderer.dart rename to packages/flutter/lib/src/renderer/io_renderer.dart diff --git a/flutter/lib/src/renderer/renderer.dart b/packages/flutter/lib/src/renderer/renderer.dart similarity index 100% rename from flutter/lib/src/renderer/renderer.dart rename to packages/flutter/lib/src/renderer/renderer.dart diff --git a/flutter/lib/src/renderer/unknown_renderer.dart b/packages/flutter/lib/src/renderer/unknown_renderer.dart similarity index 100% rename from flutter/lib/src/renderer/unknown_renderer.dart rename to packages/flutter/lib/src/renderer/unknown_renderer.dart diff --git a/flutter/lib/src/renderer/web_renderer.dart b/packages/flutter/lib/src/renderer/web_renderer.dart similarity index 100% rename from flutter/lib/src/renderer/web_renderer.dart rename to packages/flutter/lib/src/renderer/web_renderer.dart diff --git a/flutter/lib/src/replay/integration.dart b/packages/flutter/lib/src/replay/integration.dart similarity index 100% rename from flutter/lib/src/replay/integration.dart rename to packages/flutter/lib/src/replay/integration.dart diff --git a/flutter/lib/src/replay/replay_config.dart b/packages/flutter/lib/src/replay/replay_config.dart similarity index 100% rename from flutter/lib/src/replay/replay_config.dart rename to packages/flutter/lib/src/replay/replay_config.dart diff --git a/flutter/lib/src/replay/replay_quality.dart b/packages/flutter/lib/src/replay/replay_quality.dart similarity index 100% rename from flutter/lib/src/replay/replay_quality.dart rename to packages/flutter/lib/src/replay/replay_quality.dart diff --git a/flutter/lib/src/replay/replay_recorder.dart b/packages/flutter/lib/src/replay/replay_recorder.dart similarity index 100% rename from flutter/lib/src/replay/replay_recorder.dart rename to packages/flutter/lib/src/replay/replay_recorder.dart diff --git a/flutter/lib/src/replay/scheduled_recorder.dart b/packages/flutter/lib/src/replay/scheduled_recorder.dart similarity index 100% rename from flutter/lib/src/replay/scheduled_recorder.dart rename to packages/flutter/lib/src/replay/scheduled_recorder.dart diff --git a/flutter/lib/src/replay/scheduled_recorder_config.dart b/packages/flutter/lib/src/replay/scheduled_recorder_config.dart similarity index 100% rename from flutter/lib/src/replay/scheduled_recorder_config.dart rename to packages/flutter/lib/src/replay/scheduled_recorder_config.dart diff --git a/flutter/lib/src/replay/scheduler.dart b/packages/flutter/lib/src/replay/scheduler.dart similarity index 100% rename from flutter/lib/src/replay/scheduler.dart rename to packages/flutter/lib/src/replay/scheduler.dart diff --git a/flutter/lib/src/screenshot/masking_config.dart b/packages/flutter/lib/src/screenshot/masking_config.dart similarity index 100% rename from flutter/lib/src/screenshot/masking_config.dart rename to packages/flutter/lib/src/screenshot/masking_config.dart diff --git a/flutter/lib/src/screenshot/recorder.dart b/packages/flutter/lib/src/screenshot/recorder.dart similarity index 100% rename from flutter/lib/src/screenshot/recorder.dart rename to packages/flutter/lib/src/screenshot/recorder.dart diff --git a/flutter/lib/src/screenshot/recorder_config.dart b/packages/flutter/lib/src/screenshot/recorder_config.dart similarity index 100% rename from flutter/lib/src/screenshot/recorder_config.dart rename to packages/flutter/lib/src/screenshot/recorder_config.dart diff --git a/flutter/lib/src/screenshot/screenshot.dart b/packages/flutter/lib/src/screenshot/screenshot.dart similarity index 100% rename from flutter/lib/src/screenshot/screenshot.dart rename to packages/flutter/lib/src/screenshot/screenshot.dart diff --git a/flutter/lib/src/screenshot/screenshot_support.dart b/packages/flutter/lib/src/screenshot/screenshot_support.dart similarity index 100% rename from flutter/lib/src/screenshot/screenshot_support.dart rename to packages/flutter/lib/src/screenshot/screenshot_support.dart diff --git a/flutter/lib/src/screenshot/sentry_mask_widget.dart b/packages/flutter/lib/src/screenshot/sentry_mask_widget.dart similarity index 100% rename from flutter/lib/src/screenshot/sentry_mask_widget.dart rename to packages/flutter/lib/src/screenshot/sentry_mask_widget.dart diff --git a/flutter/lib/src/screenshot/sentry_screenshot_quality.dart b/packages/flutter/lib/src/screenshot/sentry_screenshot_quality.dart similarity index 100% rename from flutter/lib/src/screenshot/sentry_screenshot_quality.dart rename to packages/flutter/lib/src/screenshot/sentry_screenshot_quality.dart diff --git a/flutter/lib/src/screenshot/sentry_screenshot_widget.dart b/packages/flutter/lib/src/screenshot/sentry_screenshot_widget.dart similarity index 100% rename from flutter/lib/src/screenshot/sentry_screenshot_widget.dart rename to packages/flutter/lib/src/screenshot/sentry_screenshot_widget.dart diff --git a/flutter/lib/src/screenshot/sentry_unmask_widget.dart b/packages/flutter/lib/src/screenshot/sentry_unmask_widget.dart similarity index 100% rename from flutter/lib/src/screenshot/sentry_unmask_widget.dart rename to packages/flutter/lib/src/screenshot/sentry_unmask_widget.dart diff --git a/flutter/lib/src/screenshot/widget_filter.dart b/packages/flutter/lib/src/screenshot/widget_filter.dart similarity index 100% rename from flutter/lib/src/screenshot/widget_filter.dart rename to packages/flutter/lib/src/screenshot/widget_filter.dart diff --git a/flutter/lib/src/sentry_asset_bundle.dart b/packages/flutter/lib/src/sentry_asset_bundle.dart similarity index 100% rename from flutter/lib/src/sentry_asset_bundle.dart rename to packages/flutter/lib/src/sentry_asset_bundle.dart diff --git a/flutter/lib/src/sentry_flutter.dart b/packages/flutter/lib/src/sentry_flutter.dart similarity index 100% rename from flutter/lib/src/sentry_flutter.dart rename to packages/flutter/lib/src/sentry_flutter.dart diff --git a/flutter/lib/src/sentry_flutter_options.dart b/packages/flutter/lib/src/sentry_flutter_options.dart similarity index 100% rename from flutter/lib/src/sentry_flutter_options.dart rename to packages/flutter/lib/src/sentry_flutter_options.dart diff --git a/flutter/lib/src/sentry_privacy_options.dart b/packages/flutter/lib/src/sentry_privacy_options.dart similarity index 100% rename from flutter/lib/src/sentry_privacy_options.dart rename to packages/flutter/lib/src/sentry_privacy_options.dart diff --git a/flutter/lib/src/sentry_replay_options.dart b/packages/flutter/lib/src/sentry_replay_options.dart similarity index 100% rename from flutter/lib/src/sentry_replay_options.dart rename to packages/flutter/lib/src/sentry_replay_options.dart diff --git a/flutter/lib/src/sentry_widget.dart b/packages/flutter/lib/src/sentry_widget.dart similarity index 100% rename from flutter/lib/src/sentry_widget.dart rename to packages/flutter/lib/src/sentry_widget.dart diff --git a/flutter/lib/src/user_interaction/sentry_user_interaction_widget.dart b/packages/flutter/lib/src/user_interaction/sentry_user_interaction_widget.dart similarity index 100% rename from flutter/lib/src/user_interaction/sentry_user_interaction_widget.dart rename to packages/flutter/lib/src/user_interaction/sentry_user_interaction_widget.dart diff --git a/flutter/lib/src/user_interaction/user_interaction_info.dart b/packages/flutter/lib/src/user_interaction/user_interaction_info.dart similarity index 100% rename from flutter/lib/src/user_interaction/user_interaction_info.dart rename to packages/flutter/lib/src/user_interaction/user_interaction_info.dart diff --git a/flutter/lib/src/utils/debouncer.dart b/packages/flutter/lib/src/utils/debouncer.dart similarity index 100% rename from flutter/lib/src/utils/debouncer.dart rename to packages/flutter/lib/src/utils/debouncer.dart diff --git a/flutter/lib/src/utils/platform_dispatcher_wrapper.dart b/packages/flutter/lib/src/utils/platform_dispatcher_wrapper.dart similarity index 100% rename from flutter/lib/src/utils/platform_dispatcher_wrapper.dart rename to packages/flutter/lib/src/utils/platform_dispatcher_wrapper.dart diff --git a/flutter/lib/src/utils/timer_debouncer.dart b/packages/flutter/lib/src/utils/timer_debouncer.dart similarity index 100% rename from flutter/lib/src/utils/timer_debouncer.dart rename to packages/flutter/lib/src/utils/timer_debouncer.dart diff --git a/flutter/lib/src/version.dart b/packages/flutter/lib/src/version.dart similarity index 100% rename from flutter/lib/src/version.dart rename to packages/flutter/lib/src/version.dart diff --git a/flutter/lib/src/view_hierarchy/sentry_tree_walker.dart b/packages/flutter/lib/src/view_hierarchy/sentry_tree_walker.dart similarity index 100% rename from flutter/lib/src/view_hierarchy/sentry_tree_walker.dart rename to packages/flutter/lib/src/view_hierarchy/sentry_tree_walker.dart diff --git a/flutter/lib/src/view_hierarchy/view_hierarchy_event_processor.dart b/packages/flutter/lib/src/view_hierarchy/view_hierarchy_event_processor.dart similarity index 100% rename from flutter/lib/src/view_hierarchy/view_hierarchy_event_processor.dart rename to packages/flutter/lib/src/view_hierarchy/view_hierarchy_event_processor.dart diff --git a/flutter/lib/src/view_hierarchy/view_hierarchy_integration.dart b/packages/flutter/lib/src/view_hierarchy/view_hierarchy_integration.dart similarity index 100% rename from flutter/lib/src/view_hierarchy/view_hierarchy_integration.dart rename to packages/flutter/lib/src/view_hierarchy/view_hierarchy_integration.dart diff --git a/flutter/lib/src/web/javascript_transport.dart b/packages/flutter/lib/src/web/javascript_transport.dart similarity index 100% rename from flutter/lib/src/web/javascript_transport.dart rename to packages/flutter/lib/src/web/javascript_transport.dart diff --git a/flutter/lib/src/web/noop_sentry_js_binding.dart b/packages/flutter/lib/src/web/noop_sentry_js_binding.dart similarity index 100% rename from flutter/lib/src/web/noop_sentry_js_binding.dart rename to packages/flutter/lib/src/web/noop_sentry_js_binding.dart diff --git a/flutter/lib/src/web/script_loader/noop_script_dom_api.dart b/packages/flutter/lib/src/web/script_loader/noop_script_dom_api.dart similarity index 100% rename from flutter/lib/src/web/script_loader/noop_script_dom_api.dart rename to packages/flutter/lib/src/web/script_loader/noop_script_dom_api.dart diff --git a/flutter/lib/src/web/script_loader/script_dom_api.dart b/packages/flutter/lib/src/web/script_loader/script_dom_api.dart similarity index 100% rename from flutter/lib/src/web/script_loader/script_dom_api.dart rename to packages/flutter/lib/src/web/script_loader/script_dom_api.dart diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader.dart b/packages/flutter/lib/src/web/script_loader/sentry_script_loader.dart similarity index 100% rename from flutter/lib/src/web/script_loader/sentry_script_loader.dart rename to packages/flutter/lib/src/web/script_loader/sentry_script_loader.dart diff --git a/flutter/lib/src/web/script_loader/web_script_dom_api.dart b/packages/flutter/lib/src/web/script_loader/web_script_dom_api.dart similarity index 100% rename from flutter/lib/src/web/script_loader/web_script_dom_api.dart rename to packages/flutter/lib/src/web/script_loader/web_script_dom_api.dart diff --git a/flutter/lib/src/web/sentry_js_binding.dart b/packages/flutter/lib/src/web/sentry_js_binding.dart similarity index 100% rename from flutter/lib/src/web/sentry_js_binding.dart rename to packages/flutter/lib/src/web/sentry_js_binding.dart diff --git a/flutter/lib/src/web/sentry_js_bundle.dart b/packages/flutter/lib/src/web/sentry_js_bundle.dart similarity index 100% rename from flutter/lib/src/web/sentry_js_bundle.dart rename to packages/flutter/lib/src/web/sentry_js_bundle.dart diff --git a/flutter/lib/src/web/sentry_js_sdk_version.dart b/packages/flutter/lib/src/web/sentry_js_sdk_version.dart similarity index 100% rename from flutter/lib/src/web/sentry_js_sdk_version.dart rename to packages/flutter/lib/src/web/sentry_js_sdk_version.dart diff --git a/flutter/lib/src/web/sentry_web.dart b/packages/flutter/lib/src/web/sentry_web.dart similarity index 100% rename from flutter/lib/src/web/sentry_web.dart rename to packages/flutter/lib/src/web/sentry_web.dart diff --git a/flutter/lib/src/web/web_sentry_js_binding.dart b/packages/flutter/lib/src/web/web_sentry_js_binding.dart similarity index 100% rename from flutter/lib/src/web/web_sentry_js_binding.dart rename to packages/flutter/lib/src/web/web_sentry_js_binding.dart diff --git a/flutter/lib/src/web/web_session_handler.dart b/packages/flutter/lib/src/web/web_session_handler.dart similarity index 100% rename from flutter/lib/src/web/web_session_handler.dart rename to packages/flutter/lib/src/web/web_session_handler.dart diff --git a/flutter/lib/src/widget_utils.dart b/packages/flutter/lib/src/widget_utils.dart similarity index 100% rename from flutter/lib/src/widget_utils.dart rename to packages/flutter/lib/src/widget_utils.dart diff --git a/flutter/lib/src/widgets_binding_observer.dart b/packages/flutter/lib/src/widgets_binding_observer.dart similarity index 100% rename from flutter/lib/src/widgets_binding_observer.dart rename to packages/flutter/lib/src/widgets_binding_observer.dart diff --git a/flutter/linux/CMakeLists.txt b/packages/flutter/linux/CMakeLists.txt similarity index 100% rename from flutter/linux/CMakeLists.txt rename to packages/flutter/linux/CMakeLists.txt diff --git a/flutter/linux/sentry_flutter/sentry_flutter_plugin.h b/packages/flutter/linux/sentry_flutter/sentry_flutter_plugin.h similarity index 100% rename from flutter/linux/sentry_flutter/sentry_flutter_plugin.h rename to packages/flutter/linux/sentry_flutter/sentry_flutter_plugin.h diff --git a/flutter/macos/sentry_flutter.podspec b/packages/flutter/macos/sentry_flutter.podspec similarity index 100% rename from flutter/macos/sentry_flutter.podspec rename to packages/flutter/macos/sentry_flutter.podspec diff --git a/flutter/macos/sentry_flutter/Package.swift b/packages/flutter/macos/sentry_flutter/Package.swift similarity index 100% rename from flutter/macos/sentry_flutter/Package.swift rename to packages/flutter/macos/sentry_flutter/Package.swift diff --git a/flutter/macos/sentry_flutter/Sources/sentry_flutter/SentryFlutter.swift b/packages/flutter/macos/sentry_flutter/Sources/sentry_flutter/SentryFlutter.swift similarity index 100% rename from flutter/macos/sentry_flutter/Sources/sentry_flutter/SentryFlutter.swift rename to packages/flutter/macos/sentry_flutter/Sources/sentry_flutter/SentryFlutter.swift diff --git a/flutter/macos/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/macos/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift similarity index 100% rename from flutter/macos/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift rename to packages/flutter/macos/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift diff --git a/flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayBreadcrumbConverter.m b/packages/flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayBreadcrumbConverter.m similarity index 100% rename from flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayBreadcrumbConverter.m rename to packages/flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayBreadcrumbConverter.m diff --git a/flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayScreenshotProvider.m b/packages/flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayScreenshotProvider.m similarity index 100% rename from flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayScreenshotProvider.m rename to packages/flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterReplayScreenshotProvider.m diff --git a/flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayBreadcrumbConverter.h b/packages/flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayBreadcrumbConverter.h similarity index 100% rename from flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayBreadcrumbConverter.h rename to packages/flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayBreadcrumbConverter.h diff --git a/flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayScreenshotProvider.h b/packages/flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayScreenshotProvider.h similarity index 100% rename from flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayScreenshotProvider.h rename to packages/flutter/macos/sentry_flutter/Sources/sentry_flutter_objc/include/SentryFlutterReplayScreenshotProvider.h diff --git a/flutter/microbenchmarks/.gitignore b/packages/flutter/microbenchmarks/.gitignore similarity index 100% rename from flutter/microbenchmarks/.gitignore rename to packages/flutter/microbenchmarks/.gitignore diff --git a/flutter/microbenchmarks/.metadata b/packages/flutter/microbenchmarks/.metadata similarity index 100% rename from flutter/microbenchmarks/.metadata rename to packages/flutter/microbenchmarks/.metadata diff --git a/flutter/microbenchmarks/README.md b/packages/flutter/microbenchmarks/README.md similarity index 100% rename from flutter/microbenchmarks/README.md rename to packages/flutter/microbenchmarks/README.md diff --git a/flutter/microbenchmarks/analysis_options.yaml b/packages/flutter/microbenchmarks/analysis_options.yaml similarity index 100% rename from flutter/microbenchmarks/analysis_options.yaml rename to packages/flutter/microbenchmarks/analysis_options.yaml diff --git a/flutter/microbenchmarks/android/.gitignore b/packages/flutter/microbenchmarks/android/.gitignore similarity index 100% rename from flutter/microbenchmarks/android/.gitignore rename to packages/flutter/microbenchmarks/android/.gitignore diff --git a/flutter/microbenchmarks/android/app/build.gradle b/packages/flutter/microbenchmarks/android/app/build.gradle similarity index 100% rename from flutter/microbenchmarks/android/app/build.gradle rename to packages/flutter/microbenchmarks/android/app/build.gradle diff --git a/flutter/microbenchmarks/android/app/src/debug/AndroidManifest.xml b/packages/flutter/microbenchmarks/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from flutter/microbenchmarks/android/app/src/debug/AndroidManifest.xml rename to packages/flutter/microbenchmarks/android/app/src/debug/AndroidManifest.xml diff --git a/flutter/microbenchmarks/android/app/src/main/AndroidManifest.xml b/packages/flutter/microbenchmarks/android/app/src/main/AndroidManifest.xml similarity index 100% rename from flutter/microbenchmarks/android/app/src/main/AndroidManifest.xml rename to packages/flutter/microbenchmarks/android/app/src/main/AndroidManifest.xml diff --git a/flutter/microbenchmarks/android/app/src/main/kotlin/com/example/microbenchmarks/MainActivity.kt b/packages/flutter/microbenchmarks/android/app/src/main/kotlin/com/example/microbenchmarks/MainActivity.kt similarity index 100% rename from flutter/microbenchmarks/android/app/src/main/kotlin/com/example/microbenchmarks/MainActivity.kt rename to packages/flutter/microbenchmarks/android/app/src/main/kotlin/com/example/microbenchmarks/MainActivity.kt diff --git a/flutter/microbenchmarks/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/flutter/microbenchmarks/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from flutter/microbenchmarks/android/app/src/main/res/drawable-v21/launch_background.xml rename to packages/flutter/microbenchmarks/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/flutter/microbenchmarks/android/app/src/main/res/drawable/launch_background.xml b/packages/flutter/microbenchmarks/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from flutter/microbenchmarks/android/app/src/main/res/drawable/launch_background.xml rename to packages/flutter/microbenchmarks/android/app/src/main/res/drawable/launch_background.xml diff --git a/flutter/microbenchmarks/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/flutter/microbenchmarks/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from flutter/microbenchmarks/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/flutter/microbenchmarks/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/flutter/microbenchmarks/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/flutter/microbenchmarks/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from flutter/microbenchmarks/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/flutter/microbenchmarks/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/flutter/microbenchmarks/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/flutter/microbenchmarks/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from flutter/microbenchmarks/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/flutter/microbenchmarks/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/flutter/microbenchmarks/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/flutter/microbenchmarks/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from flutter/microbenchmarks/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/flutter/microbenchmarks/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/flutter/microbenchmarks/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/flutter/microbenchmarks/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from flutter/microbenchmarks/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/flutter/microbenchmarks/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/flutter/microbenchmarks/android/app/src/main/res/values-night/styles.xml b/packages/flutter/microbenchmarks/android/app/src/main/res/values-night/styles.xml similarity index 100% rename from flutter/microbenchmarks/android/app/src/main/res/values-night/styles.xml rename to packages/flutter/microbenchmarks/android/app/src/main/res/values-night/styles.xml diff --git a/flutter/microbenchmarks/android/app/src/main/res/values/styles.xml b/packages/flutter/microbenchmarks/android/app/src/main/res/values/styles.xml similarity index 100% rename from flutter/microbenchmarks/android/app/src/main/res/values/styles.xml rename to packages/flutter/microbenchmarks/android/app/src/main/res/values/styles.xml diff --git a/flutter/microbenchmarks/android/app/src/profile/AndroidManifest.xml b/packages/flutter/microbenchmarks/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from flutter/microbenchmarks/android/app/src/profile/AndroidManifest.xml rename to packages/flutter/microbenchmarks/android/app/src/profile/AndroidManifest.xml diff --git a/flutter/microbenchmarks/android/build.gradle b/packages/flutter/microbenchmarks/android/build.gradle similarity index 100% rename from flutter/microbenchmarks/android/build.gradle rename to packages/flutter/microbenchmarks/android/build.gradle diff --git a/flutter/microbenchmarks/android/gradle.properties b/packages/flutter/microbenchmarks/android/gradle.properties similarity index 100% rename from flutter/microbenchmarks/android/gradle.properties rename to packages/flutter/microbenchmarks/android/gradle.properties diff --git a/flutter/microbenchmarks/android/gradle/wrapper/gradle-wrapper.properties b/packages/flutter/microbenchmarks/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from flutter/microbenchmarks/android/gradle/wrapper/gradle-wrapper.properties rename to packages/flutter/microbenchmarks/android/gradle/wrapper/gradle-wrapper.properties diff --git a/flutter/microbenchmarks/android/settings.gradle b/packages/flutter/microbenchmarks/android/settings.gradle similarity index 100% rename from flutter/microbenchmarks/android/settings.gradle rename to packages/flutter/microbenchmarks/android/settings.gradle diff --git a/flutter/microbenchmarks/ios/.gitignore b/packages/flutter/microbenchmarks/ios/.gitignore similarity index 100% rename from flutter/microbenchmarks/ios/.gitignore rename to packages/flutter/microbenchmarks/ios/.gitignore diff --git a/flutter/microbenchmarks/ios/Flutter/AppFrameworkInfo.plist b/packages/flutter/microbenchmarks/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from flutter/microbenchmarks/ios/Flutter/AppFrameworkInfo.plist rename to packages/flutter/microbenchmarks/ios/Flutter/AppFrameworkInfo.plist diff --git a/flutter/microbenchmarks/ios/Flutter/Debug.xcconfig b/packages/flutter/microbenchmarks/ios/Flutter/Debug.xcconfig similarity index 100% rename from flutter/microbenchmarks/ios/Flutter/Debug.xcconfig rename to packages/flutter/microbenchmarks/ios/Flutter/Debug.xcconfig diff --git a/flutter/microbenchmarks/ios/Flutter/Release.xcconfig b/packages/flutter/microbenchmarks/ios/Flutter/Release.xcconfig similarity index 100% rename from flutter/microbenchmarks/ios/Flutter/Release.xcconfig rename to packages/flutter/microbenchmarks/ios/Flutter/Release.xcconfig diff --git a/flutter/microbenchmarks/ios/Podfile b/packages/flutter/microbenchmarks/ios/Podfile similarity index 100% rename from flutter/microbenchmarks/ios/Podfile rename to packages/flutter/microbenchmarks/ios/Podfile diff --git a/flutter/microbenchmarks/ios/Runner.xcodeproj/project.pbxproj b/packages/flutter/microbenchmarks/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from flutter/microbenchmarks/ios/Runner.xcodeproj/project.pbxproj rename to packages/flutter/microbenchmarks/ios/Runner.xcodeproj/project.pbxproj diff --git a/flutter/ios/sentry_flutter/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/packages/flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from flutter/ios/sentry_flutter/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata rename to packages/flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/flutter/microbenchmarks/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/flutter/microbenchmarks/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flutter/microbenchmarks/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from flutter/microbenchmarks/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/flutter/microbenchmarks/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/flutter/microbenchmarks/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/flutter/microbenchmarks/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from flutter/microbenchmarks/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/flutter/microbenchmarks/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/flutter/microbenchmarks/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter/microbenchmarks/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from flutter/microbenchmarks/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/flutter/microbenchmarks/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/flutter/microbenchmarks/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/flutter/microbenchmarks/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from flutter/microbenchmarks/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/flutter/microbenchmarks/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/flutter/microbenchmarks/ios/Runner/AppDelegate.swift b/packages/flutter/microbenchmarks/ios/Runner/AppDelegate.swift similarity index 100% rename from flutter/microbenchmarks/ios/Runner/AppDelegate.swift rename to packages/flutter/microbenchmarks/ios/Runner/AppDelegate.swift diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/flutter/microbenchmarks/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/flutter/microbenchmarks/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/flutter/microbenchmarks/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/flutter/microbenchmarks/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/flutter/microbenchmarks/ios/Runner/Base.lproj/Main.storyboard b/packages/flutter/microbenchmarks/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Base.lproj/Main.storyboard rename to packages/flutter/microbenchmarks/ios/Runner/Base.lproj/Main.storyboard diff --git a/flutter/microbenchmarks/ios/Runner/Info.plist b/packages/flutter/microbenchmarks/ios/Runner/Info.plist similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Info.plist rename to packages/flutter/microbenchmarks/ios/Runner/Info.plist diff --git a/flutter/microbenchmarks/ios/Runner/Runner-Bridging-Header.h b/packages/flutter/microbenchmarks/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from flutter/microbenchmarks/ios/Runner/Runner-Bridging-Header.h rename to packages/flutter/microbenchmarks/ios/Runner/Runner-Bridging-Header.h diff --git a/flutter/microbenchmarks/ios/RunnerTests/RunnerTests.swift b/packages/flutter/microbenchmarks/ios/RunnerTests/RunnerTests.swift similarity index 100% rename from flutter/microbenchmarks/ios/RunnerTests/RunnerTests.swift rename to packages/flutter/microbenchmarks/ios/RunnerTests/RunnerTests.swift diff --git a/flutter/microbenchmarks/lib/main.dart b/packages/flutter/microbenchmarks/lib/main.dart similarity index 100% rename from flutter/microbenchmarks/lib/main.dart rename to packages/flutter/microbenchmarks/lib/main.dart diff --git a/flutter/microbenchmarks/lib/src/image_bench.dart b/packages/flutter/microbenchmarks/lib/src/image_bench.dart similarity index 100% rename from flutter/microbenchmarks/lib/src/image_bench.dart rename to packages/flutter/microbenchmarks/lib/src/image_bench.dart diff --git a/flutter/microbenchmarks/lib/src/jni_bench.dart b/packages/flutter/microbenchmarks/lib/src/jni_bench.dart similarity index 100% rename from flutter/microbenchmarks/lib/src/jni_bench.dart rename to packages/flutter/microbenchmarks/lib/src/jni_bench.dart diff --git a/flutter/microbenchmarks/lib/src/memory_bench.dart b/packages/flutter/microbenchmarks/lib/src/memory_bench.dart similarity index 100% rename from flutter/microbenchmarks/lib/src/memory_bench.dart rename to packages/flutter/microbenchmarks/lib/src/memory_bench.dart diff --git a/flutter/microbenchmarks/linux/.gitignore b/packages/flutter/microbenchmarks/linux/.gitignore similarity index 100% rename from flutter/microbenchmarks/linux/.gitignore rename to packages/flutter/microbenchmarks/linux/.gitignore diff --git a/flutter/microbenchmarks/linux/CMakeLists.txt b/packages/flutter/microbenchmarks/linux/CMakeLists.txt similarity index 100% rename from flutter/microbenchmarks/linux/CMakeLists.txt rename to packages/flutter/microbenchmarks/linux/CMakeLists.txt diff --git a/flutter/microbenchmarks/linux/flutter/CMakeLists.txt b/packages/flutter/microbenchmarks/linux/flutter/CMakeLists.txt similarity index 100% rename from flutter/microbenchmarks/linux/flutter/CMakeLists.txt rename to packages/flutter/microbenchmarks/linux/flutter/CMakeLists.txt diff --git a/flutter/microbenchmarks/linux/flutter/generated_plugin_registrant.cc b/packages/flutter/microbenchmarks/linux/flutter/generated_plugin_registrant.cc similarity index 100% rename from flutter/microbenchmarks/linux/flutter/generated_plugin_registrant.cc rename to packages/flutter/microbenchmarks/linux/flutter/generated_plugin_registrant.cc diff --git a/flutter/microbenchmarks/linux/flutter/generated_plugin_registrant.h b/packages/flutter/microbenchmarks/linux/flutter/generated_plugin_registrant.h similarity index 100% rename from flutter/microbenchmarks/linux/flutter/generated_plugin_registrant.h rename to packages/flutter/microbenchmarks/linux/flutter/generated_plugin_registrant.h diff --git a/flutter/microbenchmarks/linux/flutter/generated_plugins.cmake b/packages/flutter/microbenchmarks/linux/flutter/generated_plugins.cmake similarity index 100% rename from flutter/microbenchmarks/linux/flutter/generated_plugins.cmake rename to packages/flutter/microbenchmarks/linux/flutter/generated_plugins.cmake diff --git a/flutter/microbenchmarks/linux/runner/CMakeLists.txt b/packages/flutter/microbenchmarks/linux/runner/CMakeLists.txt similarity index 100% rename from flutter/microbenchmarks/linux/runner/CMakeLists.txt rename to packages/flutter/microbenchmarks/linux/runner/CMakeLists.txt diff --git a/flutter/microbenchmarks/linux/runner/main.cc b/packages/flutter/microbenchmarks/linux/runner/main.cc similarity index 100% rename from flutter/microbenchmarks/linux/runner/main.cc rename to packages/flutter/microbenchmarks/linux/runner/main.cc diff --git a/flutter/microbenchmarks/linux/runner/my_application.cc b/packages/flutter/microbenchmarks/linux/runner/my_application.cc similarity index 100% rename from flutter/microbenchmarks/linux/runner/my_application.cc rename to packages/flutter/microbenchmarks/linux/runner/my_application.cc diff --git a/flutter/microbenchmarks/linux/runner/my_application.h b/packages/flutter/microbenchmarks/linux/runner/my_application.h similarity index 100% rename from flutter/microbenchmarks/linux/runner/my_application.h rename to packages/flutter/microbenchmarks/linux/runner/my_application.h diff --git a/flutter/microbenchmarks/macos/.gitignore b/packages/flutter/microbenchmarks/macos/.gitignore similarity index 100% rename from flutter/microbenchmarks/macos/.gitignore rename to packages/flutter/microbenchmarks/macos/.gitignore diff --git a/flutter/microbenchmarks/macos/Flutter/Flutter-Debug.xcconfig b/packages/flutter/microbenchmarks/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from flutter/microbenchmarks/macos/Flutter/Flutter-Debug.xcconfig rename to packages/flutter/microbenchmarks/macos/Flutter/Flutter-Debug.xcconfig diff --git a/flutter/microbenchmarks/macos/Flutter/Flutter-Release.xcconfig b/packages/flutter/microbenchmarks/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from flutter/microbenchmarks/macos/Flutter/Flutter-Release.xcconfig rename to packages/flutter/microbenchmarks/macos/Flutter/Flutter-Release.xcconfig diff --git a/flutter/microbenchmarks/macos/Podfile b/packages/flutter/microbenchmarks/macos/Podfile similarity index 100% rename from flutter/microbenchmarks/macos/Podfile rename to packages/flutter/microbenchmarks/macos/Podfile diff --git a/flutter/microbenchmarks/macos/Runner.xcodeproj/project.pbxproj b/packages/flutter/microbenchmarks/macos/Runner.xcodeproj/project.pbxproj similarity index 100% rename from flutter/microbenchmarks/macos/Runner.xcodeproj/project.pbxproj rename to packages/flutter/microbenchmarks/macos/Runner.xcodeproj/project.pbxproj diff --git a/flutter/microbenchmarks/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter/microbenchmarks/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from flutter/microbenchmarks/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/flutter/microbenchmarks/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/flutter/microbenchmarks/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flutter/microbenchmarks/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from flutter/microbenchmarks/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/flutter/microbenchmarks/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/flutter/microbenchmarks/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/flutter/microbenchmarks/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from flutter/microbenchmarks/macos/Runner.xcworkspace/contents.xcworkspacedata rename to packages/flutter/microbenchmarks/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/flutter/microbenchmarks/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter/microbenchmarks/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from flutter/microbenchmarks/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/flutter/microbenchmarks/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/flutter/microbenchmarks/macos/Runner/AppDelegate.swift b/packages/flutter/microbenchmarks/macos/Runner/AppDelegate.swift similarity index 100% rename from flutter/microbenchmarks/macos/Runner/AppDelegate.swift rename to packages/flutter/microbenchmarks/macos/Runner/AppDelegate.swift diff --git a/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to packages/flutter/microbenchmarks/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/flutter/microbenchmarks/macos/Runner/Base.lproj/MainMenu.xib b/packages/flutter/microbenchmarks/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Base.lproj/MainMenu.xib rename to packages/flutter/microbenchmarks/macos/Runner/Base.lproj/MainMenu.xib diff --git a/flutter/microbenchmarks/macos/Runner/Configs/AppInfo.xcconfig b/packages/flutter/microbenchmarks/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Configs/AppInfo.xcconfig rename to packages/flutter/microbenchmarks/macos/Runner/Configs/AppInfo.xcconfig diff --git a/flutter/microbenchmarks/macos/Runner/Configs/Debug.xcconfig b/packages/flutter/microbenchmarks/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Configs/Debug.xcconfig rename to packages/flutter/microbenchmarks/macos/Runner/Configs/Debug.xcconfig diff --git a/flutter/microbenchmarks/macos/Runner/Configs/Release.xcconfig b/packages/flutter/microbenchmarks/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Configs/Release.xcconfig rename to packages/flutter/microbenchmarks/macos/Runner/Configs/Release.xcconfig diff --git a/flutter/microbenchmarks/macos/Runner/Configs/Warnings.xcconfig b/packages/flutter/microbenchmarks/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Configs/Warnings.xcconfig rename to packages/flutter/microbenchmarks/macos/Runner/Configs/Warnings.xcconfig diff --git a/flutter/microbenchmarks/macos/Runner/DebugProfile.entitlements b/packages/flutter/microbenchmarks/macos/Runner/DebugProfile.entitlements similarity index 100% rename from flutter/microbenchmarks/macos/Runner/DebugProfile.entitlements rename to packages/flutter/microbenchmarks/macos/Runner/DebugProfile.entitlements diff --git a/flutter/microbenchmarks/macos/Runner/Info.plist b/packages/flutter/microbenchmarks/macos/Runner/Info.plist similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Info.plist rename to packages/flutter/microbenchmarks/macos/Runner/Info.plist diff --git a/flutter/microbenchmarks/macos/Runner/MainFlutterWindow.swift b/packages/flutter/microbenchmarks/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from flutter/microbenchmarks/macos/Runner/MainFlutterWindow.swift rename to packages/flutter/microbenchmarks/macos/Runner/MainFlutterWindow.swift diff --git a/flutter/microbenchmarks/macos/Runner/Release.entitlements b/packages/flutter/microbenchmarks/macos/Runner/Release.entitlements similarity index 100% rename from flutter/microbenchmarks/macos/Runner/Release.entitlements rename to packages/flutter/microbenchmarks/macos/Runner/Release.entitlements diff --git a/flutter/microbenchmarks/macos/RunnerTests/RunnerTests.swift b/packages/flutter/microbenchmarks/macos/RunnerTests/RunnerTests.swift similarity index 100% rename from flutter/microbenchmarks/macos/RunnerTests/RunnerTests.swift rename to packages/flutter/microbenchmarks/macos/RunnerTests/RunnerTests.swift diff --git a/flutter/microbenchmarks/pubspec.yaml b/packages/flutter/microbenchmarks/pubspec.yaml similarity index 100% rename from flutter/microbenchmarks/pubspec.yaml rename to packages/flutter/microbenchmarks/pubspec.yaml diff --git a/flutter/microbenchmarks/web/favicon.png b/packages/flutter/microbenchmarks/web/favicon.png similarity index 100% rename from flutter/microbenchmarks/web/favicon.png rename to packages/flutter/microbenchmarks/web/favicon.png diff --git a/flutter/microbenchmarks/web/icons/Icon-192.png b/packages/flutter/microbenchmarks/web/icons/Icon-192.png similarity index 100% rename from flutter/microbenchmarks/web/icons/Icon-192.png rename to packages/flutter/microbenchmarks/web/icons/Icon-192.png diff --git a/flutter/microbenchmarks/web/icons/Icon-512.png b/packages/flutter/microbenchmarks/web/icons/Icon-512.png similarity index 100% rename from flutter/microbenchmarks/web/icons/Icon-512.png rename to packages/flutter/microbenchmarks/web/icons/Icon-512.png diff --git a/flutter/microbenchmarks/web/icons/Icon-maskable-192.png b/packages/flutter/microbenchmarks/web/icons/Icon-maskable-192.png similarity index 100% rename from flutter/microbenchmarks/web/icons/Icon-maskable-192.png rename to packages/flutter/microbenchmarks/web/icons/Icon-maskable-192.png diff --git a/flutter/microbenchmarks/web/icons/Icon-maskable-512.png b/packages/flutter/microbenchmarks/web/icons/Icon-maskable-512.png similarity index 100% rename from flutter/microbenchmarks/web/icons/Icon-maskable-512.png rename to packages/flutter/microbenchmarks/web/icons/Icon-maskable-512.png diff --git a/flutter/microbenchmarks/web/index.html b/packages/flutter/microbenchmarks/web/index.html similarity index 100% rename from flutter/microbenchmarks/web/index.html rename to packages/flutter/microbenchmarks/web/index.html diff --git a/flutter/microbenchmarks/web/manifest.json b/packages/flutter/microbenchmarks/web/manifest.json similarity index 100% rename from flutter/microbenchmarks/web/manifest.json rename to packages/flutter/microbenchmarks/web/manifest.json diff --git a/flutter/example/windows/.gitignore b/packages/flutter/microbenchmarks/windows/.gitignore similarity index 94% rename from flutter/example/windows/.gitignore rename to packages/flutter/microbenchmarks/windows/.gitignore index ec4098aa65..d492d0d98c 100644 --- a/flutter/example/windows/.gitignore +++ b/packages/flutter/microbenchmarks/windows/.gitignore @@ -1,17 +1,17 @@ -flutter/ephemeral/ - -# Visual Studio user-specific files. -*.suo -*.user -*.userosscache -*.sln.docstates - -# Visual Studio build-related files. -x64/ -x86/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/flutter/microbenchmarks/windows/CMakeLists.txt b/packages/flutter/microbenchmarks/windows/CMakeLists.txt similarity index 100% rename from flutter/microbenchmarks/windows/CMakeLists.txt rename to packages/flutter/microbenchmarks/windows/CMakeLists.txt diff --git a/flutter/example/windows/flutter/CMakeLists.txt b/packages/flutter/microbenchmarks/windows/flutter/CMakeLists.txt similarity index 97% rename from flutter/example/windows/flutter/CMakeLists.txt rename to packages/flutter/microbenchmarks/windows/flutter/CMakeLists.txt index efb62ebe7d..903f4899d6 100644 --- a/flutter/example/windows/flutter/CMakeLists.txt +++ b/packages/flutter/microbenchmarks/windows/flutter/CMakeLists.txt @@ -1,109 +1,109 @@ -# This file controls Flutter-level build steps. It should not be edited. -cmake_minimum_required(VERSION 3.14) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. -set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") - -# Set fallback configurations for older versions of the flutter tool. -if (NOT DEFINED FLUTTER_TARGET_PLATFORM) - set(FLUTTER_TARGET_PLATFORM "windows-x64") -endif() - -# === Flutter Library === -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "flutter_export.h" - "flutter_windows.h" - "flutter_messenger.h" - "flutter_plugin_registrar.h" - "flutter_texture_registrar.h" -) -list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") -add_dependencies(flutter flutter_assemble) - -# === Wrapper === -list(APPEND CPP_WRAPPER_SOURCES_CORE - "core_implementations.cc" - "standard_codec.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_PLUGIN - "plugin_registrar.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_APP - "flutter_engine.cc" - "flutter_view_controller.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") - -# Wrapper sources needed for a plugin. -add_library(flutter_wrapper_plugin STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} -) -apply_standard_settings(flutter_wrapper_plugin) -set_target_properties(flutter_wrapper_plugin PROPERTIES - POSITION_INDEPENDENT_CODE ON) -set_target_properties(flutter_wrapper_plugin PROPERTIES - CXX_VISIBILITY_PRESET hidden) -target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) -target_include_directories(flutter_wrapper_plugin PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_plugin flutter_assemble) - -# Wrapper sources needed for the runner. -add_library(flutter_wrapper_app STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_APP} -) -apply_standard_settings(flutter_wrapper_app) -target_link_libraries(flutter_wrapper_app PUBLIC flutter) -target_include_directories(flutter_wrapper_app PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_app flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") -set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} - ${PHONY_OUTPUT} - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - ${FLUTTER_TARGET_PLATFORM} $ - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} -) +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/flutter/microbenchmarks/windows/flutter/generated_plugin_registrant.cc b/packages/flutter/microbenchmarks/windows/flutter/generated_plugin_registrant.cc similarity index 100% rename from flutter/microbenchmarks/windows/flutter/generated_plugin_registrant.cc rename to packages/flutter/microbenchmarks/windows/flutter/generated_plugin_registrant.cc diff --git a/flutter/microbenchmarks/windows/flutter/generated_plugin_registrant.h b/packages/flutter/microbenchmarks/windows/flutter/generated_plugin_registrant.h similarity index 100% rename from flutter/microbenchmarks/windows/flutter/generated_plugin_registrant.h rename to packages/flutter/microbenchmarks/windows/flutter/generated_plugin_registrant.h diff --git a/flutter/microbenchmarks/windows/flutter/generated_plugins.cmake b/packages/flutter/microbenchmarks/windows/flutter/generated_plugins.cmake similarity index 100% rename from flutter/microbenchmarks/windows/flutter/generated_plugins.cmake rename to packages/flutter/microbenchmarks/windows/flutter/generated_plugins.cmake diff --git a/flutter/example/windows/runner/CMakeLists.txt b/packages/flutter/microbenchmarks/windows/runner/CMakeLists.txt similarity index 97% rename from flutter/example/windows/runner/CMakeLists.txt rename to packages/flutter/microbenchmarks/windows/runner/CMakeLists.txt index 2041a04410..394917c053 100644 --- a/flutter/example/windows/runner/CMakeLists.txt +++ b/packages/flutter/microbenchmarks/windows/runner/CMakeLists.txt @@ -1,40 +1,40 @@ -cmake_minimum_required(VERSION 3.14) -project(runner LANGUAGES CXX) - -# Define the application target. To change its name, change BINARY_NAME in the -# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer -# work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} WIN32 - "flutter_window.cpp" - "main.cpp" - "utils.cpp" - "win32_window.cpp" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" - "Runner.rc" - "runner.exe.manifest" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add preprocessor definitions for the build version. -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") - -# Disable Windows macros that collide with C++ standard library functions. -target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") - -# Add dependency libraries and include directories. Add any application-specific -# dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) -target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") -target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") - -# Run the Flutter tool portions of the build. This must not be removed. -add_dependencies(${BINARY_NAME} flutter_assemble) +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/flutter/microbenchmarks/windows/runner/Runner.rc b/packages/flutter/microbenchmarks/windows/runner/Runner.rc similarity index 100% rename from flutter/microbenchmarks/windows/runner/Runner.rc rename to packages/flutter/microbenchmarks/windows/runner/Runner.rc diff --git a/flutter/example/windows/runner/flutter_window.cpp b/packages/flutter/microbenchmarks/windows/runner/flutter_window.cpp similarity index 96% rename from flutter/example/windows/runner/flutter_window.cpp rename to packages/flutter/microbenchmarks/windows/runner/flutter_window.cpp index c819cb083f..955ee3038f 100644 --- a/flutter/example/windows/runner/flutter_window.cpp +++ b/packages/flutter/microbenchmarks/windows/runner/flutter_window.cpp @@ -1,71 +1,71 @@ -#include "flutter_window.h" - -#include - -#include "flutter/generated_plugin_registrant.h" - -FlutterWindow::FlutterWindow(const flutter::DartProject& project) - : project_(project) {} - -FlutterWindow::~FlutterWindow() {} - -bool FlutterWindow::OnCreate() { - if (!Win32Window::OnCreate()) { - return false; - } - - RECT frame = GetClientArea(); - - // The size here must match the window dimensions to avoid unnecessary surface - // creation / destruction in the startup path. - flutter_controller_ = std::make_unique( - frame.right - frame.left, frame.bottom - frame.top, project_); - // Ensure that basic setup of the controller was successful. - if (!flutter_controller_->engine() || !flutter_controller_->view()) { - return false; - } - RegisterPlugins(flutter_controller_->engine()); - SetChildContent(flutter_controller_->view()->GetNativeWindow()); - - flutter_controller_->engine()->SetNextFrameCallback([&]() { - this->Show(); - }); - - // Flutter can complete the first frame before the "show window" callback is - // registered. The following call ensures a frame is pending to ensure the - // window is shown. It is a no-op if the first frame hasn't completed yet. - flutter_controller_->ForceRedraw(); - - return true; -} - -void FlutterWindow::OnDestroy() { - if (flutter_controller_) { - flutter_controller_ = nullptr; - } - - Win32Window::OnDestroy(); -} - -LRESULT -FlutterWindow::MessageHandler(HWND hwnd, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - // Give Flutter, including plugins, an opportunity to handle window messages. - if (flutter_controller_) { - std::optional result = - flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, - lparam); - if (result) { - return *result; - } - } - - switch (message) { - case WM_FONTCHANGE: - flutter_controller_->engine()->ReloadSystemFonts(); - break; - } - - return Win32Window::MessageHandler(hwnd, message, wparam, lparam); -} +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/flutter/example/windows/runner/flutter_window.h b/packages/flutter/microbenchmarks/windows/runner/flutter_window.h similarity index 96% rename from flutter/example/windows/runner/flutter_window.h rename to packages/flutter/microbenchmarks/windows/runner/flutter_window.h index 28c23839b9..6da0652f05 100644 --- a/flutter/example/windows/runner/flutter_window.h +++ b/packages/flutter/microbenchmarks/windows/runner/flutter_window.h @@ -1,33 +1,33 @@ -#ifndef RUNNER_FLUTTER_WINDOW_H_ -#define RUNNER_FLUTTER_WINDOW_H_ - -#include -#include - -#include - -#include "win32_window.h" - -// A window that does nothing but host a Flutter view. -class FlutterWindow : public Win32Window { - public: - // Creates a new FlutterWindow hosting a Flutter view running |project|. - explicit FlutterWindow(const flutter::DartProject& project); - virtual ~FlutterWindow(); - - protected: - // Win32Window: - bool OnCreate() override; - void OnDestroy() override; - LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept override; - - private: - // The project to run. - flutter::DartProject project_; - - // The Flutter instance hosted by this window. - std::unique_ptr flutter_controller_; -}; - -#endif // RUNNER_FLUTTER_WINDOW_H_ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/flutter/microbenchmarks/windows/runner/main.cpp b/packages/flutter/microbenchmarks/windows/runner/main.cpp similarity index 100% rename from flutter/microbenchmarks/windows/runner/main.cpp rename to packages/flutter/microbenchmarks/windows/runner/main.cpp diff --git a/flutter/example/windows/runner/resource.h b/packages/flutter/microbenchmarks/windows/runner/resource.h similarity index 96% rename from flutter/example/windows/runner/resource.h rename to packages/flutter/microbenchmarks/windows/runner/resource.h index ddc7f3efc0..66a65d1e4a 100644 --- a/flutter/example/windows/runner/resource.h +++ b/packages/flutter/microbenchmarks/windows/runner/resource.h @@ -1,16 +1,16 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by Runner.rc -// -#define IDI_APP_ICON 101 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/flutter/microbenchmarks/windows/runner/resources/app_icon.ico b/packages/flutter/microbenchmarks/windows/runner/resources/app_icon.ico similarity index 100% rename from flutter/microbenchmarks/windows/runner/resources/app_icon.ico rename to packages/flutter/microbenchmarks/windows/runner/resources/app_icon.ico diff --git a/flutter/microbenchmarks/windows/runner/runner.exe.manifest b/packages/flutter/microbenchmarks/windows/runner/runner.exe.manifest similarity index 100% rename from flutter/microbenchmarks/windows/runner/runner.exe.manifest rename to packages/flutter/microbenchmarks/windows/runner/runner.exe.manifest diff --git a/flutter/microbenchmarks/windows/runner/utils.cpp b/packages/flutter/microbenchmarks/windows/runner/utils.cpp similarity index 100% rename from flutter/microbenchmarks/windows/runner/utils.cpp rename to packages/flutter/microbenchmarks/windows/runner/utils.cpp diff --git a/flutter/example/windows/runner/utils.h b/packages/flutter/microbenchmarks/windows/runner/utils.h similarity index 97% rename from flutter/example/windows/runner/utils.h rename to packages/flutter/microbenchmarks/windows/runner/utils.h index 3f0e05cba3..3879d54755 100644 --- a/flutter/example/windows/runner/utils.h +++ b/packages/flutter/microbenchmarks/windows/runner/utils.h @@ -1,19 +1,19 @@ -#ifndef RUNNER_UTILS_H_ -#define RUNNER_UTILS_H_ - -#include -#include - -// Creates a console for the process, and redirects stdout and stderr to -// it for both the runner and the Flutter library. -void CreateAndAttachConsole(); - -// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string -// encoded in UTF-8. Returns an empty std::string on failure. -std::string Utf8FromUtf16(const wchar_t* utf16_string); - -// Gets the command line arguments passed in as a std::vector, -// encoded in UTF-8. Returns an empty std::vector on failure. -std::vector GetCommandLineArguments(); - -#endif // RUNNER_UTILS_H_ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/flutter/example/windows/runner/win32_window.cpp b/packages/flutter/microbenchmarks/windows/runner/win32_window.cpp similarity index 96% rename from flutter/example/windows/runner/win32_window.cpp rename to packages/flutter/microbenchmarks/windows/runner/win32_window.cpp index b5ba2a099f..60608d0fe5 100644 --- a/flutter/example/windows/runner/win32_window.cpp +++ b/packages/flutter/microbenchmarks/windows/runner/win32_window.cpp @@ -1,288 +1,288 @@ -#include "win32_window.h" - -#include -#include - -#include "resource.h" - -namespace { - -/// Window attribute that enables dark mode window decorations. -/// -/// Redefined in case the developer's machine has a Windows SDK older than -/// version 10.0.22000.0. -/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute -#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE -#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 -#endif - -constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; - -/// Registry key for app theme preference. -/// -/// A value of 0 indicates apps should use dark mode. A non-zero or missing -/// value indicates apps should use light mode. -constexpr const wchar_t kGetPreferredBrightnessRegKey[] = - L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; -constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; - -// The number of Win32Window objects that currently exist. -static int g_active_window_count = 0; - -using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); - -// Scale helper to convert logical scaler values to physical using passed in -// scale factor -int Scale(int source, double scale_factor) { - return static_cast(source * scale_factor); -} - -// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. -// This API is only needed for PerMonitor V1 awareness mode. -void EnableFullDpiSupportIfAvailable(HWND hwnd) { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) { - return; - } - auto enable_non_client_dpi_scaling = - reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) { - enable_non_client_dpi_scaling(hwnd); - } - FreeLibrary(user32_module); -} - -} // namespace - -// Manages the Win32Window's window class registration. -class WindowClassRegistrar { - public: - ~WindowClassRegistrar() = default; - - // Returns the singleton registrar instance. - static WindowClassRegistrar* GetInstance() { - if (!instance_) { - instance_ = new WindowClassRegistrar(); - } - return instance_; - } - - // Returns the name of the window class, registering the class if it hasn't - // previously been registered. - const wchar_t* GetWindowClass(); - - // Unregisters the window class. Should only be called if there are no - // instances of the window. - void UnregisterWindowClass(); - - private: - WindowClassRegistrar() = default; - - static WindowClassRegistrar* instance_; - - bool class_registered_ = false; -}; - -WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; - -const wchar_t* WindowClassRegistrar::GetWindowClass() { - if (!class_registered_) { - WNDCLASS window_class{}; - window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); - window_class.lpszClassName = kWindowClassName; - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = GetModuleHandle(nullptr); - window_class.hIcon = - LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); - window_class.hbrBackground = 0; - window_class.lpszMenuName = nullptr; - window_class.lpfnWndProc = Win32Window::WndProc; - RegisterClass(&window_class); - class_registered_ = true; - } - return kWindowClassName; -} - -void WindowClassRegistrar::UnregisterWindowClass() { - UnregisterClass(kWindowClassName, nullptr); - class_registered_ = false; -} - -Win32Window::Win32Window() { - ++g_active_window_count; -} - -Win32Window::~Win32Window() { - --g_active_window_count; - Destroy(); -} - -bool Win32Window::Create(const std::wstring& title, - const Point& origin, - const Size& size) { - Destroy(); - - const wchar_t* window_class = - WindowClassRegistrar::GetInstance()->GetWindowClass(); - - const POINT target_point = {static_cast(origin.x), - static_cast(origin.y)}; - HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); - UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); - double scale_factor = dpi / 96.0; - - HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW, - Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), - Scale(size.width, scale_factor), Scale(size.height, scale_factor), - nullptr, nullptr, GetModuleHandle(nullptr), this); - - if (!window) { - return false; - } - - UpdateTheme(window); - - return OnCreate(); -} - -bool Win32Window::Show() { - return ShowWindow(window_handle_, SW_SHOWNORMAL); -} - -// static -LRESULT CALLBACK Win32Window::WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - if (message == WM_NCCREATE) { - auto window_struct = reinterpret_cast(lparam); - SetWindowLongPtr(window, GWLP_USERDATA, - reinterpret_cast(window_struct->lpCreateParams)); - - auto that = static_cast(window_struct->lpCreateParams); - EnableFullDpiSupportIfAvailable(window); - that->window_handle_ = window; - } else if (Win32Window* that = GetThisFromHandle(window)) { - return that->MessageHandler(window, message, wparam, lparam); - } - - return DefWindowProc(window, message, wparam, lparam); -} - -LRESULT -Win32Window::MessageHandler(HWND hwnd, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - switch (message) { - case WM_DESTROY: - window_handle_ = nullptr; - Destroy(); - if (quit_on_close_) { - PostQuitMessage(0); - } - return 0; - - case WM_DPICHANGED: { - auto newRectSize = reinterpret_cast(lparam); - LONG newWidth = newRectSize->right - newRectSize->left; - LONG newHeight = newRectSize->bottom - newRectSize->top; - - SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, - newHeight, SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; - } - case WM_SIZE: { - RECT rect = GetClientArea(); - if (child_content_ != nullptr) { - // Size and position the child window. - MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, TRUE); - } - return 0; - } - - case WM_ACTIVATE: - if (child_content_ != nullptr) { - SetFocus(child_content_); - } - return 0; - - case WM_DWMCOLORIZATIONCOLORCHANGED: - UpdateTheme(hwnd); - return 0; - } - - return DefWindowProc(window_handle_, message, wparam, lparam); -} - -void Win32Window::Destroy() { - OnDestroy(); - - if (window_handle_) { - DestroyWindow(window_handle_); - window_handle_ = nullptr; - } - if (g_active_window_count == 0) { - WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); - } -} - -Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { - return reinterpret_cast( - GetWindowLongPtr(window, GWLP_USERDATA)); -} - -void Win32Window::SetChildContent(HWND content) { - child_content_ = content; - SetParent(content, window_handle_); - RECT frame = GetClientArea(); - - MoveWindow(content, frame.left, frame.top, frame.right - frame.left, - frame.bottom - frame.top, true); - - SetFocus(child_content_); -} - -RECT Win32Window::GetClientArea() { - RECT frame; - GetClientRect(window_handle_, &frame); - return frame; -} - -HWND Win32Window::GetHandle() { - return window_handle_; -} - -void Win32Window::SetQuitOnClose(bool quit_on_close) { - quit_on_close_ = quit_on_close; -} - -bool Win32Window::OnCreate() { - // No-op; provided for subclasses. - return true; -} - -void Win32Window::OnDestroy() { - // No-op; provided for subclasses. -} - -void Win32Window::UpdateTheme(HWND const window) { - DWORD light_mode; - DWORD light_mode_size = sizeof(light_mode); - LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, - kGetPreferredBrightnessRegValue, - RRF_RT_REG_DWORD, nullptr, &light_mode, - &light_mode_size); - - if (result == ERROR_SUCCESS) { - BOOL enable_dark_mode = light_mode == 0; - DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, - &enable_dark_mode, sizeof(enable_dark_mode)); - } -} +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/flutter/example/windows/runner/win32_window.h b/packages/flutter/microbenchmarks/windows/runner/win32_window.h similarity index 97% rename from flutter/example/windows/runner/win32_window.h rename to packages/flutter/microbenchmarks/windows/runner/win32_window.h index 49b847f075..e901dde684 100644 --- a/flutter/example/windows/runner/win32_window.h +++ b/packages/flutter/microbenchmarks/windows/runner/win32_window.h @@ -1,102 +1,102 @@ -#ifndef RUNNER_WIN32_WINDOW_H_ -#define RUNNER_WIN32_WINDOW_H_ - -#include - -#include -#include -#include - -// A class abstraction for a high DPI-aware Win32 Window. Intended to be -// inherited from by classes that wish to specialize with custom -// rendering and input handling -class Win32Window { - public: - struct Point { - unsigned int x; - unsigned int y; - Point(unsigned int x, unsigned int y) : x(x), y(y) {} - }; - - struct Size { - unsigned int width; - unsigned int height; - Size(unsigned int width, unsigned int height) - : width(width), height(height) {} - }; - - Win32Window(); - virtual ~Win32Window(); - - // Creates a win32 window with |title| that is positioned and sized using - // |origin| and |size|. New windows are created on the default monitor. Window - // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size this function will scale the inputted width and height as - // as appropriate for the default monitor. The window is invisible until - // |Show| is called. Returns true if the window was created successfully. - bool Create(const std::wstring& title, const Point& origin, const Size& size); - - // Show the current window. Returns true if the window was successfully shown. - bool Show(); - - // Release OS resources associated with window. - void Destroy(); - - // Inserts |content| into the window tree. - void SetChildContent(HWND content); - - // Returns the backing Window handle to enable clients to set icon and other - // window properties. Returns nullptr if the window has been destroyed. - HWND GetHandle(); - - // If true, closing this window will quit the application. - void SetQuitOnClose(bool quit_on_close); - - // Return a RECT representing the bounds of the current client area. - RECT GetClientArea(); - - protected: - // Processes and route salient window messages for mouse handling, - // size change and DPI. Delegates handling of these to member overloads that - // inheriting classes can handle. - virtual LRESULT MessageHandler(HWND window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Called when CreateAndShow is called, allowing subclass window-related - // setup. Subclasses should return false if setup fails. - virtual bool OnCreate(); - - // Called when Destroy is called. - virtual void OnDestroy(); - - private: - friend class WindowClassRegistrar; - - // OS callback called by message pump. Handles the WM_NCCREATE message which - // is passed when the non-client area is being created and enables automatic - // non-client DPI scaling so that the non-client area automatically - // responds to changes in DPI. All other messages are handled by - // MessageHandler. - static LRESULT CALLBACK WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Retrieves a class instance pointer for |window| - static Win32Window* GetThisFromHandle(HWND const window) noexcept; - - // Update the window frame's theme to match the system theme. - static void UpdateTheme(HWND const window); - - bool quit_on_close_ = false; - - // window handle for top level window. - HWND window_handle_ = nullptr; - - // window handle for hosted content. - HWND child_content_ = nullptr; -}; - -#endif // RUNNER_WIN32_WINDOW_H_ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml similarity index 100% rename from flutter/pubspec.yaml rename to packages/flutter/pubspec.yaml diff --git a/flutter/pubspec_overrides.yaml b/packages/flutter/pubspec_overrides.yaml similarity index 100% rename from flutter/pubspec_overrides.yaml rename to packages/flutter/pubspec_overrides.yaml diff --git a/flutter/scripts/generate-cocoa-bindings.sh b/packages/flutter/scripts/generate-cocoa-bindings.sh similarity index 100% rename from flutter/scripts/generate-cocoa-bindings.sh rename to packages/flutter/scripts/generate-cocoa-bindings.sh diff --git a/flutter/scripts/generate-jni-bindings.sh b/packages/flutter/scripts/generate-jni-bindings.sh similarity index 100% rename from flutter/scripts/generate-jni-bindings.sh rename to packages/flutter/scripts/generate-jni-bindings.sh diff --git a/flutter/scripts/generate-native-bindings.ps1 b/packages/flutter/scripts/generate-native-bindings.ps1 similarity index 100% rename from flutter/scripts/generate-native-bindings.ps1 rename to packages/flutter/scripts/generate-native-bindings.ps1 diff --git a/flutter/scripts/update-android.sh b/packages/flutter/scripts/update-android.sh similarity index 100% rename from flutter/scripts/update-android.sh rename to packages/flutter/scripts/update-android.sh diff --git a/flutter/scripts/update-cocoa.sh b/packages/flutter/scripts/update-cocoa.sh similarity index 100% rename from flutter/scripts/update-cocoa.sh rename to packages/flutter/scripts/update-cocoa.sh diff --git a/flutter/scripts/update-js.sh b/packages/flutter/scripts/update-js.sh similarity index 100% rename from flutter/scripts/update-js.sh rename to packages/flutter/scripts/update-js.sh diff --git a/flutter/scripts/update-native.sh b/packages/flutter/scripts/update-native.sh similarity index 100% rename from flutter/scripts/update-native.sh rename to packages/flutter/scripts/update-native.sh diff --git a/flutter/sentry-native/CMakeCache.txt b/packages/flutter/sentry-native/CMakeCache.txt similarity index 100% rename from flutter/sentry-native/CMakeCache.txt rename to packages/flutter/sentry-native/CMakeCache.txt diff --git a/flutter/sentry-native/sentry-native.cmake b/packages/flutter/sentry-native/sentry-native.cmake similarity index 100% rename from flutter/sentry-native/sentry-native.cmake rename to packages/flutter/sentry-native/sentry-native.cmake diff --git a/flutter/test/android_platform_exception_event_processor_test.dart b/packages/flutter/test/android_platform_exception_event_processor_test.dart similarity index 100% rename from flutter/test/android_platform_exception_event_processor_test.dart rename to packages/flutter/test/android_platform_exception_event_processor_test.dart diff --git a/flutter/test/binding.dart b/packages/flutter/test/binding.dart similarity index 100% rename from flutter/test/binding.dart rename to packages/flutter/test/binding.dart diff --git a/flutter/test/event_processor/flutter_enricher_event_processor_test.dart b/packages/flutter/test/event_processor/flutter_enricher_event_processor_test.dart similarity index 100% rename from flutter/test/event_processor/flutter_enricher_event_processor_test.dart rename to packages/flutter/test/event_processor/flutter_enricher_event_processor_test.dart diff --git a/flutter/test/event_processor/flutter_exception_event_processor_test.dart b/packages/flutter/test/event_processor/flutter_exception_event_processor_test.dart similarity index 100% rename from flutter/test/event_processor/flutter_exception_event_processor_test.dart rename to packages/flutter/test/event_processor/flutter_exception_event_processor_test.dart diff --git a/flutter/test/event_processor/platform_exception_event_processor_test.dart b/packages/flutter/test/event_processor/platform_exception_event_processor_test.dart similarity index 100% rename from flutter/test/event_processor/platform_exception_event_processor_test.dart rename to packages/flutter/test/event_processor/platform_exception_event_processor_test.dart diff --git a/flutter/test/event_processor/screenshot_event_processor_test.dart b/packages/flutter/test/event_processor/screenshot_event_processor_test.dart similarity index 100% rename from flutter/test/event_processor/screenshot_event_processor_test.dart rename to packages/flutter/test/event_processor/screenshot_event_processor_test.dart diff --git a/flutter/test/event_processor/url_filter/io_filter_event_processor_test.dart b/packages/flutter/test/event_processor/url_filter/io_filter_event_processor_test.dart similarity index 100% rename from flutter/test/event_processor/url_filter/io_filter_event_processor_test.dart rename to packages/flutter/test/event_processor/url_filter/io_filter_event_processor_test.dart diff --git a/flutter/test/event_processor/url_filter/web_url_filter_event_processor_test.dart b/packages/flutter/test/event_processor/url_filter/web_url_filter_event_processor_test.dart similarity index 100% rename from flutter/test/event_processor/url_filter/web_url_filter_event_processor_test.dart rename to packages/flutter/test/event_processor/url_filter/web_url_filter_event_processor_test.dart diff --git a/flutter/test/event_processor/widget_event_processor_test.dart b/packages/flutter/test/event_processor/widget_event_processor_test.dart similarity index 100% rename from flutter/test/event_processor/widget_event_processor_test.dart rename to packages/flutter/test/event_processor/widget_event_processor_test.dart diff --git a/flutter/test/fake_frame_callback_handler.dart b/packages/flutter/test/fake_frame_callback_handler.dart similarity index 100% rename from flutter/test/fake_frame_callback_handler.dart rename to packages/flutter/test/fake_frame_callback_handler.dart diff --git a/flutter/test/feedback/sentry_feedback_widget_test.dart b/packages/flutter/test/feedback/sentry_feedback_widget_test.dart similarity index 100% rename from flutter/test/feedback/sentry_feedback_widget_test.dart rename to packages/flutter/test/feedback/sentry_feedback_widget_test.dart diff --git a/flutter/test/file_system_transport_test.dart b/packages/flutter/test/file_system_transport_test.dart similarity index 100% rename from flutter/test/file_system_transport_test.dart rename to packages/flutter/test/file_system_transport_test.dart diff --git a/flutter/test/flutter_sentry_attachment_test.dart b/packages/flutter/test/flutter_sentry_attachment_test.dart similarity index 100% rename from flutter/test/flutter_sentry_attachment_test.dart rename to packages/flutter/test/flutter_sentry_attachment_test.dart diff --git a/flutter/test/flutter_version_test.dart b/packages/flutter/test/flutter_version_test.dart similarity index 100% rename from flutter/test/flutter_version_test.dart rename to packages/flutter/test/flutter_version_test.dart diff --git a/flutter/test/frame_tracking/frames_tracking_integration_test.dart b/packages/flutter/test/frame_tracking/frames_tracking_integration_test.dart similarity index 100% rename from flutter/test/frame_tracking/frames_tracking_integration_test.dart rename to packages/flutter/test/frame_tracking/frames_tracking_integration_test.dart diff --git a/flutter/test/frame_tracking/sentry_delayed_frames_tracker_test.dart b/packages/flutter/test/frame_tracking/sentry_delayed_frames_tracker_test.dart similarity index 100% rename from flutter/test/frame_tracking/sentry_delayed_frames_tracker_test.dart rename to packages/flutter/test/frame_tracking/sentry_delayed_frames_tracker_test.dart diff --git a/flutter/test/frame_tracking/span_frame_metrics_collector_test.dart b/packages/flutter/test/frame_tracking/span_frame_metrics_collector_test.dart similarity index 100% rename from flutter/test/frame_tracking/span_frame_metrics_collector_test.dart rename to packages/flutter/test/frame_tracking/span_frame_metrics_collector_test.dart diff --git a/flutter/test/frame_tracking/span_frame_metrics_test.dart b/packages/flutter/test/frame_tracking/span_frame_metrics_test.dart similarity index 100% rename from flutter/test/frame_tracking/span_frame_metrics_test.dart rename to packages/flutter/test/frame_tracking/span_frame_metrics_test.dart diff --git a/flutter/test/initialization_test.dart b/packages/flutter/test/initialization_test.dart similarity index 100% rename from flutter/test/initialization_test.dart rename to packages/flutter/test/initialization_test.dart diff --git a/flutter/test/integrations/connectivity_integration_test.dart b/packages/flutter/test/integrations/connectivity_integration_test.dart similarity index 100% rename from flutter/test/integrations/connectivity_integration_test.dart rename to packages/flutter/test/integrations/connectivity_integration_test.dart diff --git a/flutter/test/integrations/debug_print_integration_test.dart b/packages/flutter/test/integrations/debug_print_integration_test.dart similarity index 100% rename from flutter/test/integrations/debug_print_integration_test.dart rename to packages/flutter/test/integrations/debug_print_integration_test.dart diff --git a/flutter/test/integrations/fixture.dart b/packages/flutter/test/integrations/fixture.dart similarity index 100% rename from flutter/test/integrations/fixture.dart rename to packages/flutter/test/integrations/fixture.dart diff --git a/flutter/test/integrations/flutter_error_integration_test.dart b/packages/flutter/test/integrations/flutter_error_integration_test.dart similarity index 100% rename from flutter/test/integrations/flutter_error_integration_test.dart rename to packages/flutter/test/integrations/flutter_error_integration_test.dart diff --git a/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart b/packages/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart similarity index 100% rename from flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart rename to packages/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart diff --git a/flutter/test/integrations/generic_app_start_integration_test.dart b/packages/flutter/test/integrations/generic_app_start_integration_test.dart similarity index 100% rename from flutter/test/integrations/generic_app_start_integration_test.dart rename to packages/flutter/test/integrations/generic_app_start_integration_test.dart diff --git a/flutter/test/integrations/init_native_sdk_test.dart b/packages/flutter/test/integrations/init_native_sdk_test.dart similarity index 100% rename from flutter/test/integrations/init_native_sdk_test.dart rename to packages/flutter/test/integrations/init_native_sdk_test.dart diff --git a/flutter/test/integrations/load_contexts_integration_test.dart b/packages/flutter/test/integrations/load_contexts_integration_test.dart similarity index 100% rename from flutter/test/integrations/load_contexts_integration_test.dart rename to packages/flutter/test/integrations/load_contexts_integration_test.dart diff --git a/flutter/test/integrations/load_contexts_integrations_test.dart b/packages/flutter/test/integrations/load_contexts_integrations_test.dart similarity index 100% rename from flutter/test/integrations/load_contexts_integrations_test.dart rename to packages/flutter/test/integrations/load_contexts_integrations_test.dart diff --git a/flutter/test/integrations/load_native_debug_images_integration_test.dart b/packages/flutter/test/integrations/load_native_debug_images_integration_test.dart similarity index 100% rename from flutter/test/integrations/load_native_debug_images_integration_test.dart rename to packages/flutter/test/integrations/load_native_debug_images_integration_test.dart diff --git a/flutter/test/integrations/load_release_integration_test.dart b/packages/flutter/test/integrations/load_release_integration_test.dart similarity index 100% rename from flutter/test/integrations/load_release_integration_test.dart rename to packages/flutter/test/integrations/load_release_integration_test.dart diff --git a/flutter/test/integrations/mock_platform_dispatcher.dart b/packages/flutter/test/integrations/mock_platform_dispatcher.dart similarity index 100% rename from flutter/test/integrations/mock_platform_dispatcher.dart rename to packages/flutter/test/integrations/mock_platform_dispatcher.dart diff --git a/flutter/test/integrations/native_app_start_handler_test.dart b/packages/flutter/test/integrations/native_app_start_handler_test.dart similarity index 100% rename from flutter/test/integrations/native_app_start_handler_test.dart rename to packages/flutter/test/integrations/native_app_start_handler_test.dart diff --git a/flutter/test/integrations/native_app_start_integration_test.dart b/packages/flutter/test/integrations/native_app_start_integration_test.dart similarity index 100% rename from flutter/test/integrations/native_app_start_integration_test.dart rename to packages/flutter/test/integrations/native_app_start_integration_test.dart diff --git a/flutter/test/integrations/native_sdk_integration_test.dart b/packages/flutter/test/integrations/native_sdk_integration_test.dart similarity index 100% rename from flutter/test/integrations/native_sdk_integration_test.dart rename to packages/flutter/test/integrations/native_sdk_integration_test.dart diff --git a/flutter/test/integrations/not_initialized_widgets_binding_on_error_integration_test.dart b/packages/flutter/test/integrations/not_initialized_widgets_binding_on_error_integration_test.dart similarity index 100% rename from flutter/test/integrations/not_initialized_widgets_binding_on_error_integration_test.dart rename to packages/flutter/test/integrations/not_initialized_widgets_binding_on_error_integration_test.dart diff --git a/flutter/test/integrations/not_initialized_widgets_binding_test.dart b/packages/flutter/test/integrations/not_initialized_widgets_binding_test.dart similarity index 100% rename from flutter/test/integrations/not_initialized_widgets_binding_test.dart rename to packages/flutter/test/integrations/not_initialized_widgets_binding_test.dart diff --git a/flutter/test/integrations/on_error_integration_test.dart b/packages/flutter/test/integrations/on_error_integration_test.dart similarity index 100% rename from flutter/test/integrations/on_error_integration_test.dart rename to packages/flutter/test/integrations/on_error_integration_test.dart diff --git a/flutter/test/integrations/screenshot_integration_test.dart b/packages/flutter/test/integrations/screenshot_integration_test.dart similarity index 100% rename from flutter/test/integrations/screenshot_integration_test.dart rename to packages/flutter/test/integrations/screenshot_integration_test.dart diff --git a/flutter/test/integrations/web_sdk_integration_test.dart b/packages/flutter/test/integrations/web_sdk_integration_test.dart similarity index 100% rename from flutter/test/integrations/web_sdk_integration_test.dart rename to packages/flutter/test/integrations/web_sdk_integration_test.dart diff --git a/flutter/test/integrations/web_session_integration_test.dart b/packages/flutter/test/integrations/web_session_integration_test.dart similarity index 100% rename from flutter/test/integrations/web_session_integration_test.dart rename to packages/flutter/test/integrations/web_session_integration_test.dart diff --git a/flutter/test/integrations/widgets_binding_integration_test.dart b/packages/flutter/test/integrations/widgets_binding_integration_test.dart similarity index 100% rename from flutter/test/integrations/widgets_binding_integration_test.dart rename to packages/flutter/test/integrations/widgets_binding_integration_test.dart diff --git a/flutter/test/integrations/widgets_flutter_binding_integration_test.dart b/packages/flutter/test/integrations/widgets_flutter_binding_integration_test.dart similarity index 100% rename from flutter/test/integrations/widgets_flutter_binding_integration_test.dart rename to packages/flutter/test/integrations/widgets_flutter_binding_integration_test.dart diff --git a/flutter/test/jvm/jvm_exception_test.dart b/packages/flutter/test/jvm/jvm_exception_test.dart similarity index 100% rename from flutter/test/jvm/jvm_exception_test.dart rename to packages/flutter/test/jvm/jvm_exception_test.dart diff --git a/flutter/test/jvm/jvm_frame_test.dart b/packages/flutter/test/jvm/jvm_frame_test.dart similarity index 100% rename from flutter/test/jvm/jvm_frame_test.dart rename to packages/flutter/test/jvm/jvm_frame_test.dart diff --git a/flutter/test/method_channel_helper_test.dart b/packages/flutter/test/method_channel_helper_test.dart similarity index 100% rename from flutter/test/method_channel_helper_test.dart rename to packages/flutter/test/method_channel_helper_test.dart diff --git a/flutter/test/mocks.dart b/packages/flutter/test/mocks.dart similarity index 100% rename from flutter/test/mocks.dart rename to packages/flutter/test/mocks.dart diff --git a/flutter/test/mocks.mocks.dart b/packages/flutter/test/mocks.mocks.dart similarity index 100% rename from flutter/test/mocks.mocks.dart rename to packages/flutter/test/mocks.mocks.dart diff --git a/flutter/test/native_memory_test.dart b/packages/flutter/test/native_memory_test.dart similarity index 100% rename from flutter/test/native_memory_test.dart rename to packages/flutter/test/native_memory_test.dart diff --git a/flutter/test/native_memory_web_mock.dart b/packages/flutter/test/native_memory_web_mock.dart similarity index 100% rename from flutter/test/native_memory_web_mock.dart rename to packages/flutter/test/native_memory_web_mock.dart diff --git a/flutter/test/native_scope_observer_test.dart b/packages/flutter/test/native_scope_observer_test.dart similarity index 100% rename from flutter/test/native_scope_observer_test.dart rename to packages/flutter/test/native_scope_observer_test.dart diff --git a/flutter/test/navigation/sentry_display_test.dart b/packages/flutter/test/navigation/sentry_display_test.dart similarity index 100% rename from flutter/test/navigation/sentry_display_test.dart rename to packages/flutter/test/navigation/sentry_display_test.dart diff --git a/flutter/test/navigation/sentry_display_widget_test.dart b/packages/flutter/test/navigation/sentry_display_widget_test.dart similarity index 100% rename from flutter/test/navigation/sentry_display_widget_test.dart rename to packages/flutter/test/navigation/sentry_display_widget_test.dart diff --git a/flutter/test/navigation/sentry_navigator_observer_test.dart b/packages/flutter/test/navigation/sentry_navigator_observer_test.dart similarity index 100% rename from flutter/test/navigation/sentry_navigator_observer_test.dart rename to packages/flutter/test/navigation/sentry_navigator_observer_test.dart diff --git a/flutter/test/navigation/sentry_navigator_observer_traces_test.dart b/packages/flutter/test/navigation/sentry_navigator_observer_traces_test.dart similarity index 100% rename from flutter/test/navigation/sentry_navigator_observer_traces_test.dart rename to packages/flutter/test/navigation/sentry_navigator_observer_traces_test.dart diff --git a/flutter/test/navigation/time_to_display_tracker_test.dart b/packages/flutter/test/navigation/time_to_display_tracker_test.dart similarity index 100% rename from flutter/test/navigation/time_to_display_tracker_test.dart rename to packages/flutter/test/navigation/time_to_display_tracker_test.dart diff --git a/flutter/test/navigation/time_to_full_display_tracker_test.dart b/packages/flutter/test/navigation/time_to_full_display_tracker_test.dart similarity index 100% rename from flutter/test/navigation/time_to_full_display_tracker_test.dart rename to packages/flutter/test/navigation/time_to_full_display_tracker_test.dart diff --git a/flutter/test/navigation/time_to_initial_display_tracker_test.dart b/packages/flutter/test/navigation/time_to_initial_display_tracker_test.dart similarity index 100% rename from flutter/test/navigation/time_to_initial_display_tracker_test.dart rename to packages/flutter/test/navigation/time_to_initial_display_tracker_test.dart diff --git a/flutter/test/no_such_method_provider.dart b/packages/flutter/test/no_such_method_provider.dart similarity index 100% rename from flutter/test/no_such_method_provider.dart rename to packages/flutter/test/no_such_method_provider.dart diff --git a/flutter/test/profiling_test.dart b/packages/flutter/test/profiling_test.dart similarity index 100% rename from flutter/test/profiling_test.dart rename to packages/flutter/test/profiling_test.dart diff --git a/flutter/test/replay/android_replay_recorder_web.dart b/packages/flutter/test/replay/android_replay_recorder_web.dart similarity index 100% rename from flutter/test/replay/android_replay_recorder_web.dart rename to packages/flutter/test/replay/android_replay_recorder_web.dart diff --git a/flutter/test/replay/replay_event_processor_test.dart b/packages/flutter/test/replay/replay_event_processor_test.dart similarity index 100% rename from flutter/test/replay/replay_event_processor_test.dart rename to packages/flutter/test/replay/replay_event_processor_test.dart diff --git a/flutter/test/replay/replay_integration_test.dart b/packages/flutter/test/replay/replay_integration_test.dart similarity index 100% rename from flutter/test/replay/replay_integration_test.dart rename to packages/flutter/test/replay/replay_integration_test.dart diff --git a/flutter/test/replay/replay_native_test.dart b/packages/flutter/test/replay/replay_native_test.dart similarity index 100% rename from flutter/test/replay/replay_native_test.dart rename to packages/flutter/test/replay/replay_native_test.dart diff --git a/flutter/test/replay/replay_test_util.dart b/packages/flutter/test/replay/replay_test_util.dart similarity index 100% rename from flutter/test/replay/replay_test_util.dart rename to packages/flutter/test/replay/replay_test_util.dart diff --git a/flutter/test/replay/scheduled_recorder_test.dart b/packages/flutter/test/replay/scheduled_recorder_test.dart similarity index 100% rename from flutter/test/replay/scheduled_recorder_test.dart rename to packages/flutter/test/replay/scheduled_recorder_test.dart diff --git a/flutter/test/replay/scheduler_test.dart b/packages/flutter/test/replay/scheduler_test.dart similarity index 100% rename from flutter/test/replay/scheduler_test.dart rename to packages/flutter/test/replay/scheduler_test.dart diff --git a/flutter/test/screenshot/masking_config_test.dart b/packages/flutter/test/screenshot/masking_config_test.dart similarity index 100% rename from flutter/test/screenshot/masking_config_test.dart rename to packages/flutter/test/screenshot/masking_config_test.dart diff --git a/flutter/test/screenshot/recorder_config_test.dart b/packages/flutter/test/screenshot/recorder_config_test.dart similarity index 100% rename from flutter/test/screenshot/recorder_config_test.dart rename to packages/flutter/test/screenshot/recorder_config_test.dart diff --git a/flutter/test/screenshot/recorder_test.dart b/packages/flutter/test/screenshot/recorder_test.dart similarity index 100% rename from flutter/test/screenshot/recorder_test.dart rename to packages/flutter/test/screenshot/recorder_test.dart diff --git a/flutter/test/screenshot/screenshot_support_test.dart b/packages/flutter/test/screenshot/screenshot_support_test.dart similarity index 100% rename from flutter/test/screenshot/screenshot_support_test.dart rename to packages/flutter/test/screenshot/screenshot_support_test.dart diff --git a/flutter/test/screenshot/sentry_screenshot_quality_test.dart b/packages/flutter/test/screenshot/sentry_screenshot_quality_test.dart similarity index 100% rename from flutter/test/screenshot/sentry_screenshot_quality_test.dart rename to packages/flutter/test/screenshot/sentry_screenshot_quality_test.dart diff --git a/flutter/test/screenshot/sentry_screenshot_widget_test.dart b/packages/flutter/test/screenshot/sentry_screenshot_widget_test.dart similarity index 100% rename from flutter/test/screenshot/sentry_screenshot_widget_test.dart rename to packages/flutter/test/screenshot/sentry_screenshot_widget_test.dart diff --git a/flutter/test/screenshot/sentry_screenshot_widget_test.mocks.dart b/packages/flutter/test/screenshot/sentry_screenshot_widget_test.mocks.dart similarity index 100% rename from flutter/test/screenshot/sentry_screenshot_widget_test.mocks.dart rename to packages/flutter/test/screenshot/sentry_screenshot_widget_test.mocks.dart diff --git a/flutter/test/screenshot/test_widget.dart b/packages/flutter/test/screenshot/test_widget.dart similarity index 100% rename from flutter/test/screenshot/test_widget.dart rename to packages/flutter/test/screenshot/test_widget.dart diff --git a/flutter/test/screenshot/widget_filter_test.dart b/packages/flutter/test/screenshot/widget_filter_test.dart similarity index 100% rename from flutter/test/screenshot/widget_filter_test.dart rename to packages/flutter/test/screenshot/widget_filter_test.dart diff --git a/flutter/test/sentry_asset_bundle_test.dart b/packages/flutter/test/sentry_asset_bundle_test.dart similarity index 100% rename from flutter/test/sentry_asset_bundle_test.dart rename to packages/flutter/test/sentry_asset_bundle_test.dart diff --git a/flutter/test/sentry_flutter_options_test.dart b/packages/flutter/test/sentry_flutter_options_test.dart similarity index 100% rename from flutter/test/sentry_flutter_options_test.dart rename to packages/flutter/test/sentry_flutter_options_test.dart diff --git a/flutter/test/sentry_flutter_test.dart b/packages/flutter/test/sentry_flutter_test.dart similarity index 100% rename from flutter/test/sentry_flutter_test.dart rename to packages/flutter/test/sentry_flutter_test.dart diff --git a/flutter/test/sentry_flutter_util.dart b/packages/flutter/test/sentry_flutter_util.dart similarity index 100% rename from flutter/test/sentry_flutter_util.dart rename to packages/flutter/test/sentry_flutter_util.dart diff --git a/flutter/test/sentry_native/sentry_native_test.dart b/packages/flutter/test/sentry_native/sentry_native_test.dart similarity index 100% rename from flutter/test/sentry_native/sentry_native_test.dart rename to packages/flutter/test/sentry_native/sentry_native_test.dart diff --git a/flutter/test/sentry_native/sentry_native_test_ffi.dart b/packages/flutter/test/sentry_native/sentry_native_test_ffi.dart similarity index 100% rename from flutter/test/sentry_native/sentry_native_test_ffi.dart rename to packages/flutter/test/sentry_native/sentry_native_test_ffi.dart diff --git a/flutter/test/sentry_native/sentry_native_test_web.dart b/packages/flutter/test/sentry_native/sentry_native_test_web.dart similarity index 100% rename from flutter/test/sentry_native/sentry_native_test_web.dart rename to packages/flutter/test/sentry_native/sentry_native_test_web.dart diff --git a/flutter/test/sentry_native_channel_test.dart b/packages/flutter/test/sentry_native_channel_test.dart similarity index 100% rename from flutter/test/sentry_native_channel_test.dart rename to packages/flutter/test/sentry_native_channel_test.dart diff --git a/flutter/test/sentry_widget_test.dart b/packages/flutter/test/sentry_widget_test.dart similarity index 100% rename from flutter/test/sentry_widget_test.dart rename to packages/flutter/test/sentry_widget_test.dart diff --git a/flutter/test/sentry_widgets_binding_mixin_test.dart b/packages/flutter/test/sentry_widgets_binding_mixin_test.dart similarity index 100% rename from flutter/test/sentry_widgets_binding_mixin_test.dart rename to packages/flutter/test/sentry_widgets_binding_mixin_test.dart diff --git a/flutter/test/sentry_widgets_flutter_binding_test.dart b/packages/flutter/test/sentry_widgets_flutter_binding_test.dart similarity index 100% rename from flutter/test/sentry_widgets_flutter_binding_test.dart rename to packages/flutter/test/sentry_widgets_flutter_binding_test.dart diff --git a/flutter/test/user_interaction/sentry_user_interaction_widget_test.dart b/packages/flutter/test/user_interaction/sentry_user_interaction_widget_test.dart similarity index 100% rename from flutter/test/user_interaction/sentry_user_interaction_widget_test.dart rename to packages/flutter/test/user_interaction/sentry_user_interaction_widget_test.dart diff --git a/flutter/test/utils/debouncer_test.dart b/packages/flutter/test/utils/debouncer_test.dart similarity index 100% rename from flutter/test/utils/debouncer_test.dart rename to packages/flutter/test/utils/debouncer_test.dart diff --git a/flutter/test/version_test.dart b/packages/flutter/test/version_test.dart similarity index 100% rename from flutter/test/version_test.dart rename to packages/flutter/test/version_test.dart diff --git a/flutter/test/view_hierarchy/sentry_tree_walker_test.dart b/packages/flutter/test/view_hierarchy/sentry_tree_walker_test.dart similarity index 100% rename from flutter/test/view_hierarchy/sentry_tree_walker_test.dart rename to packages/flutter/test/view_hierarchy/sentry_tree_walker_test.dart diff --git a/flutter/test/view_hierarchy/view_hierarchy_event_processor_test.dart b/packages/flutter/test/view_hierarchy/view_hierarchy_event_processor_test.dart similarity index 100% rename from flutter/test/view_hierarchy/view_hierarchy_event_processor_test.dart rename to packages/flutter/test/view_hierarchy/view_hierarchy_event_processor_test.dart diff --git a/flutter/test/view_hierarchy/view_hierarchy_integration_test.dart b/packages/flutter/test/view_hierarchy/view_hierarchy_integration_test.dart similarity index 100% rename from flutter/test/view_hierarchy/view_hierarchy_integration_test.dart rename to packages/flutter/test/view_hierarchy/view_hierarchy_integration_test.dart diff --git a/flutter/test/web/sentry_js_bundles_test.dart b/packages/flutter/test/web/sentry_js_bundles_test.dart similarity index 100% rename from flutter/test/web/sentry_js_bundles_test.dart rename to packages/flutter/test/web/sentry_js_bundles_test.dart diff --git a/flutter/test/web/sentry_script_loader_test.dart b/packages/flutter/test/web/sentry_script_loader_test.dart similarity index 100% rename from flutter/test/web/sentry_script_loader_test.dart rename to packages/flutter/test/web/sentry_script_loader_test.dart diff --git a/flutter/test/web/sentry_script_loader_tt_custom_test.dart b/packages/flutter/test/web/sentry_script_loader_tt_custom_test.dart similarity index 100% rename from flutter/test/web/sentry_script_loader_tt_custom_test.dart rename to packages/flutter/test/web/sentry_script_loader_tt_custom_test.dart diff --git a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart b/packages/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart similarity index 100% rename from flutter/test/web/sentry_script_loader_tt_forbidden_test.dart rename to packages/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart diff --git a/flutter/test/web/sentry_web_test.dart b/packages/flutter/test/web/sentry_web_test.dart similarity index 100% rename from flutter/test/web/sentry_web_test.dart rename to packages/flutter/test/web/sentry_web_test.dart diff --git a/flutter/test/web/utils.dart b/packages/flutter/test/web/utils.dart similarity index 100% rename from flutter/test/web/utils.dart rename to packages/flutter/test/web/utils.dart diff --git a/flutter/test/web/web_sentry_js_binding_test.dart b/packages/flutter/test/web/web_sentry_js_binding_test.dart similarity index 100% rename from flutter/test/web/web_sentry_js_binding_test.dart rename to packages/flutter/test/web/web_sentry_js_binding_test.dart diff --git a/flutter/test/web/web_session_handler_test.dart b/packages/flutter/test/web/web_session_handler_test.dart similarity index 100% rename from flutter/test/web/web_session_handler_test.dart rename to packages/flutter/test/web/web_session_handler_test.dart diff --git a/flutter/test/widgets_binding_observer_test.dart b/packages/flutter/test/widgets_binding_observer_test.dart similarity index 100% rename from flutter/test/widgets_binding_observer_test.dart rename to packages/flutter/test/widgets_binding_observer_test.dart diff --git a/flutter/tool/presubmit.sh b/packages/flutter/tool/presubmit.sh similarity index 100% rename from flutter/tool/presubmit.sh rename to packages/flutter/tool/presubmit.sh diff --git a/flutter/windows/.gitignore b/packages/flutter/windows/.gitignore similarity index 94% rename from flutter/windows/.gitignore rename to packages/flutter/windows/.gitignore index 808064a0fa..b3eb2be169 100644 --- a/flutter/windows/.gitignore +++ b/packages/flutter/windows/.gitignore @@ -1,17 +1,17 @@ -flutter/ - -# Visual Studio user-specific files. -*.suo -*.user -*.userosscache -*.sln.docstates - -# Visual Studio build-related files. -x64/ -x86/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/flutter/windows/CMakeLists.txt b/packages/flutter/windows/CMakeLists.txt similarity index 97% rename from flutter/windows/CMakeLists.txt rename to packages/flutter/windows/CMakeLists.txt index 9b1fcbcc5e..d8064dc4b9 100644 --- a/flutter/windows/CMakeLists.txt +++ b/packages/flutter/windows/CMakeLists.txt @@ -1,19 +1,19 @@ -# The Flutter tooling requires that developers have a version of Visual Studio -# installed that includes CMake 3.14 or later. You should not increase this -# version, as doing so will cause the plugin to fail to compile for some -# customers of the plugin. -cmake_minimum_required(VERSION 3.14) - -if(FLUTTER_TARGET_PLATFORM EQUAL "windows-arm64") - set(native_backend "breakpad") -else() - set(native_backend "crashpad") -endif() - -set(SENTRY_BACKEND ${native_backend} CACHE STRING "The sentry backend responsible for reporting crashes" FORCE) - -include("${CMAKE_CURRENT_SOURCE_DIR}/../sentry-native/sentry-native.cmake") - -# Even though sentry_flutter doesn't actually provide a useful plugin, we need to accommodate the Flutter tooling. -# sentry_flutter/sentry_flutter_plugin.h is included by the flutter-tool generated plugin registrar: -target_include_directories(sentry INTERFACE ${CMAKE_CURRENT_LIST_DIR}) +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. You should not increase this +# version, as doing so will cause the plugin to fail to compile for some +# customers of the plugin. +cmake_minimum_required(VERSION 3.14) + +if(FLUTTER_TARGET_PLATFORM EQUAL "windows-arm64") + set(native_backend "breakpad") +else() + set(native_backend "crashpad") +endif() + +set(SENTRY_BACKEND ${native_backend} CACHE STRING "The sentry backend responsible for reporting crashes" FORCE) + +include("${CMAKE_CURRENT_SOURCE_DIR}/../sentry-native/sentry-native.cmake") + +# Even though sentry_flutter doesn't actually provide a useful plugin, we need to accommodate the Flutter tooling. +# sentry_flutter/sentry_flutter_plugin.h is included by the flutter-tool generated plugin registrar: +target_include_directories(sentry INTERFACE ${CMAKE_CURRENT_LIST_DIR}) diff --git a/flutter/windows/sentry_flutter/sentry_flutter_plugin.h b/packages/flutter/windows/sentry_flutter/sentry_flutter_plugin.h similarity index 100% rename from flutter/windows/sentry_flutter/sentry_flutter_plugin.h rename to packages/flutter/windows/sentry_flutter/sentry_flutter_plugin.h From de2a13078cc20e33644dbc5735aebdcc8e68c552 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 14:14:44 +0200 Subject: [PATCH 37/45] Update --- .github/workflows/flutter.yml | 22 +++++++------- .github/workflows/flutter_test.yml | 22 +++++++------- melos.yaml | 3 ++ packages/flutter/CHANGELOG.md | 2 +- packages/flutter/dartdoc_options.yaml | 2 +- .../flutter/example/pubspec_overrides.yaml | 29 +++++-------------- .../microbenchmarks/pubspec_overrides.yaml | 6 ++++ packages/flutter/pubspec_overrides.yaml | 3 +- 8 files changed, 43 insertions(+), 46 deletions(-) create mode 100644 packages/flutter/microbenchmarks/pubspec_overrides.yaml diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 45dbf50fc9..be48175008 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -12,8 +12,8 @@ on: - '.github/actions/coverage/**' - '!**/*.md' - '!**/class-diagram.svg' - - 'dart/**' - - 'flutter/**' + - 'packages/dart/**' + - 'packages/flutter/**' - 'metrics/flutter.properties' jobs: @@ -61,20 +61,20 @@ jobs: - uses: ./.github/actions/flutter-test with: - directory: flutter + directory: packages/flutter - uses: ./.github/actions/coverage if: matrix.target == 'linux' && matrix.sdk == 'stable' with: token: ${{ secrets.CODECOV_TOKEN }} - directory: flutter + directory: packages/flutter coverage: sentry_flutter min-coverage: 90 - name: Build example for ${{ matrix.target }} # The example currently doesn't support compiling for WASM. Should be OK once we add package:web in v9. if: matrix.target != 'wasm' - working-directory: flutter/example + working-directory: packages/flutter/example shell: bash run: | flutter config --enable-windows-desktop @@ -110,7 +110,7 @@ jobs: defaults: run: shell: bash - working-directory: flutter/example + working-directory: packages/flutter/example strategy: fail-fast: false matrix: @@ -136,7 +136,7 @@ jobs: analyze: uses: ./.github/workflows/analyze.yml with: - package: flutter + package: packages/flutter sdk: flutter panaThreshold: 87 @@ -145,7 +145,7 @@ jobs: timeout-minutes: 20 defaults: run: - working-directory: ./flutter + working-directory: packages/flutter steps: - uses: actions/checkout@v4 # https://github.com/CocoaPods/CocoaPods/issues/5275#issuecomment-315461879 @@ -156,7 +156,7 @@ jobs: timeout-minutes: 20 defaults: run: - working-directory: ./flutter + working-directory: packages/flutter steps: - uses: actions/checkout@v4 - uses: norio-nomura/action-swiftlint@9f4dcd7fd46b4e75d7935cf2f4df406d5cae3684 # pin@3.2.1 @@ -168,7 +168,7 @@ jobs: timeout-minutes: 20 defaults: run: - working-directory: ./flutter + working-directory: packages/flutter steps: - uses: actions/checkout@v4 @@ -188,4 +188,4 @@ jobs: # To recreate baseline run: detekt -i flutter/android,flutter/example/android -b flutter/config/detekt-bl.xml -cb - uses: natiginfo/action-detekt-all@45229fbbe47eaff1160b6c956d7ffe14dc23c206 # pin@1.23.8 with: - args: -i flutter/android,flutter/example/android --baseline flutter/config/detekt-bl.xml --jvm-target 1.8 --build-upon-default-config --all-rules + args: -i packages/flutter/android,packages/flutter/example/android --baseline packages/flutter/config/detekt-bl.xml --jvm-target 1.8 --build-upon-default-config --all-rules diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index f5c84b24a3..e29bd73dce 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -9,8 +9,8 @@ on: - '!**/*.md' - '!**/class-diagram.svg' - '.github/workflows/flutter_test.yml' - - 'dart/**' - - 'flutter/**' + - 'packages/dart/**' + - 'packages/flutter/**' env: SENTRY_AUTH_TOKEN_E2E: ${{ secrets.SENTRY_AUTH_TOKEN_E2E }} @@ -29,7 +29,7 @@ jobs: timeout-minutes: 30 defaults: run: - working-directory: ./flutter/example + working-directory: packages/flutter/example strategy: fail-fast: false matrix: @@ -61,13 +61,13 @@ jobs: # TODO: fix emulator caching, in ubuntu-latest emulator won't boot: https://github.com/ReactiveCircus/android-emulator-runner/issues/278 - name: build apk - working-directory: ./flutter/example/android + working-directory: packages/flutter/example/android run: flutter build apk --debug --target-platform=android-x64 - name: launch android emulator & run android native test uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed #pin@v2.34.0 with: - working-directory: ./flutter/example/android + working-directory: packages/flutter/example/android api-level: 31 profile: Nexus 6 arch: x86_64 @@ -80,7 +80,7 @@ jobs: - name: launch android emulator & run android integration test uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed #pin@v2.34.0 with: - working-directory: ./flutter/example + working-directory: packages/flutter/example api-level: 31 profile: Nexus 6 arch: x86_64 @@ -93,7 +93,7 @@ jobs: - name: launch android emulator & run android integration test in profile mode uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed #pin@v2.34.0 with: - working-directory: ./flutter/example + working-directory: packages/flutter/example api-level: 31 profile: Nexus 6 arch: x86_64 @@ -109,7 +109,7 @@ jobs: timeout-minutes: 30 defaults: run: - working-directory: ./flutter/example + working-directory: packages/flutter/example strategy: fail-fast: false matrix: @@ -127,7 +127,7 @@ jobs: - run: flutter pub get - run: pod install - working-directory: ./flutter/example/${{ matrix.target }} + working-directory: packages/flutter/example/${{ matrix.target }} - name: prepare test device id: device @@ -156,7 +156,7 @@ jobs: # We only have the native unit test package in the iOS xcodeproj at the moment. # Should be OK because it will likely be removed after switching to FFI (see https://github.com/getsentry/sentry-dart/issues/1444). if: ${{ matrix.target != 'macos' }} - working-directory: ./flutter/example/${{ matrix.target }} + working-directory: packages/flutter/example/${{ matrix.target }} run: xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug -destination "platform=$DEVICE_PLATFORM" -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO env: DEVICE_PLATFORM: ${{ steps.device.outputs.platform }} @@ -166,7 +166,7 @@ jobs: timeout-minutes: 30 defaults: run: - working-directory: ./flutter/example + working-directory: packages/flutter/example strategy: fail-fast: false matrix: diff --git a/melos.yaml b/melos.yaml index f19567daf2..266b31ebd6 100644 --- a/melos.yaml +++ b/melos.yaml @@ -2,6 +2,9 @@ name: sentry_dart_workspace packages: - 'packages/**' +ignore: + - 'packages/flutter/example/**' # ignore it for now + ide: intellij: moduleNamePrefix: '' diff --git a/packages/flutter/CHANGELOG.md b/packages/flutter/CHANGELOG.md index 04c99a55ca..699cc9e7b7 120000 --- a/packages/flutter/CHANGELOG.md +++ b/packages/flutter/CHANGELOG.md @@ -1 +1 @@ -../CHANGELOG.md \ No newline at end of file +../../CHANGELOG.md \ No newline at end of file diff --git a/packages/flutter/dartdoc_options.yaml b/packages/flutter/dartdoc_options.yaml index fcb0f9cc26..7cbb8c0d74 120000 --- a/packages/flutter/dartdoc_options.yaml +++ b/packages/flutter/dartdoc_options.yaml @@ -1 +1 @@ -../packages/dart/dartdoc_options.yaml \ No newline at end of file +../dart/dartdoc_options.yaml \ No newline at end of file diff --git a/packages/flutter/example/pubspec_overrides.yaml b/packages/flutter/example/pubspec_overrides.yaml index 47527b0a71..7a00c71ed9 100644 --- a/packages/flutter/example/pubspec_overrides.yaml +++ b/packages/flutter/example/pubspec_overrides.yaml @@ -1,29 +1,16 @@ +# melos_managed_dependency_overrides: sentry,sentry_dio,sentry_drift,sentry_file,sentry_flutter,sentry_hive,sentry_logging dependency_overrides: sentry: - path: ../../packages/dart - sentry_flutter: - path: ../ + path: ../../dart sentry_dio: path: ../../dio - sentry_logging: - path: ../../logging - sentry_sqflite: - path: ../../sqflite + sentry_drift: + path: ../../drift sentry_file: path: ../../file + sentry_flutter: + path: .. sentry_hive: path: ../../hive - sentry_drift: - path: ../../drift - sentry_isar: - path: ../../isar - # isar community fork is more stable - isar: - version: ^3.1.0 - hosted: https://pub.isar-community.dev/ - isar_flutter_libs: - version: ^3.1.0 - hosted: https://pub.isar-community.dev/ - isar_generator: - version: ^3.1.0 - hosted: https://pub.isar-community.dev/ + sentry_logging: + path: ../../logging diff --git a/packages/flutter/microbenchmarks/pubspec_overrides.yaml b/packages/flutter/microbenchmarks/pubspec_overrides.yaml new file mode 100644 index 0000000000..20369303f2 --- /dev/null +++ b/packages/flutter/microbenchmarks/pubspec_overrides.yaml @@ -0,0 +1,6 @@ +# melos_managed_dependency_overrides: sentry,sentry_flutter +dependency_overrides: + sentry: + path: ../../dart + sentry_flutter: + path: .. diff --git a/packages/flutter/pubspec_overrides.yaml b/packages/flutter/pubspec_overrides.yaml index 0a17afda20..04ac16fe75 100644 --- a/packages/flutter/pubspec_overrides.yaml +++ b/packages/flutter/pubspec_overrides.yaml @@ -1,3 +1,4 @@ +# melos_managed_dependency_overrides: sentry dependency_overrides: sentry: - path: ../packages/dart + path: ../dart From 9e8d11344d5ac5cde6acea509abd62596771ced5 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 14:21:45 +0200 Subject: [PATCH 38/45] Update --- packages/flutter/example/integration_test/profiling_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/example/integration_test/profiling_test.dart b/packages/flutter/example/integration_test/profiling_test.dart index 0ebf4d1c0c..ef9efef711 100644 --- a/packages/flutter/example/integration_test/profiling_test.dart +++ b/packages/flutter/example/integration_test/profiling_test.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import '../../../packages/dart/test/mocks/mock_transport.dart'; +import '../../../dart/test/mocks/mock_transport.dart'; void main() { final transport = MockTransport(); From 00c8f619daf8b409340bad3da09072bbe8ee45ec Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 14:28:04 +0200 Subject: [PATCH 39/45] Update --- melos.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/melos.yaml b/melos.yaml index 266b31ebd6..4a68f7a054 100644 --- a/melos.yaml +++ b/melos.yaml @@ -3,7 +3,7 @@ packages: - 'packages/**' ignore: - - 'packages/flutter/example/**' # ignore it for now + - 'packages/flutter/example/' # ignore it for now ide: intellij: From ff475264af7b3d1d2ca89d79c76cc3d22a9b9d22 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 14:32:39 +0200 Subject: [PATCH 40/45] Update --- .github/file-filters.yml | 14 +++++++------- .github/workflows/analyze.yml | 2 +- .github/workflows/firebase_remote_config.yml | 2 +- .github/workflows/flutter.yml | 2 +- .github/workflows/metrics.yml | 2 +- .github/workflows/min_version_test.yml | 2 +- .github/workflows/testflight.yml | 8 ++++---- .github/workflows/update-deps.yml | 8 ++++---- .github/workflows/web-example-ghpages.yml | 4 ++-- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/file-filters.yml b/.github/file-filters.yml index 7280ac9201..911be318c1 100644 --- a/.github/file-filters.yml +++ b/.github/file-filters.yml @@ -1,10 +1,10 @@ # This is used by the action https://github.com/dorny/paths-filter high_risk_code: &high_risk_code - - "flutter/lib/src/span_frame_metrics_collector.dart" - - "flutter/lib/src/integrations/native_app_start_integration.dart" - - "flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt" - - "flutter/ios/Classes/SentryFlutterPluginApple.swift" - - "flutter/lib/src/screenshot/recorder.dart" - - "flutter/lib/src/screenshot/widget_filter.dart" - - "flutter/lib/src/native/java/android_replay_recorder.dart" + - "packages/flutter/lib/src/span_frame_metrics_collector.dart" + - "packages/flutter/lib/src/integrations/native_app_start_integration.dart" + - "packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt" + - "packages/flutter/ios/Classes/SentryFlutterPluginApple.swift" + - "packages/flutter/lib/src/screenshot/recorder.dart" + - "packages/flutter/lib/src/screenshot/widget_filter.dart" + - "packages/flutter/lib/src/native/java/android_replay_recorder.dart" diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml index 0a4bc81055..f5115f52fc 100644 --- a/.github/workflows/analyze.yml +++ b/.github/workflows/analyze.yml @@ -45,7 +45,7 @@ jobs: - run: flutter pub get if: ${{ inputs.package == 'flutter' }} - working-directory: flutter/microbenchmarks + working-directory: packages/flutter/microbenchmarks - run: dart format --set-exit-if-changed ./ diff --git a/.github/workflows/firebase_remote_config.yml b/.github/workflows/firebase_remote_config.yml index 09ad1f6a93..e443731b70 100644 --- a/.github/workflows/firebase_remote_config.yml +++ b/.github/workflows/firebase_remote_config.yml @@ -13,7 +13,7 @@ on: - '.github/actions/dart-test/**' - '.github/actions/coverage/**' - 'dart/**' - - 'flutter/**' + - 'packages/flutter/**' - 'firebase_remote_config/**' # https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index be48175008..c79fb7ed45 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -185,7 +185,7 @@ jobs: timeout-minutes: 20 steps: - uses: actions/checkout@v4 - # To recreate baseline run: detekt -i flutter/android,flutter/example/android -b flutter/config/detekt-bl.xml -cb + # To recreate baseline run: detekt -i packages/flutter/android,packages/flutter/example/android -b packages/flutter/config/detekt-bl.xml -cb - uses: natiginfo/action-detekt-all@45229fbbe47eaff1160b6c956d7ffe14dc23c206 # pin@1.23.8 with: args: -i packages/flutter/android,packages/flutter/example/android --baseline packages/flutter/config/detekt-bl.xml --jvm-target 1.8 --build-upon-default-config --all-rules diff --git a/.github/workflows/metrics.yml b/.github/workflows/metrics.yml index 8dc92ebe40..2dd64a0997 100644 --- a/.github/workflows/metrics.yml +++ b/.github/workflows/metrics.yml @@ -5,7 +5,7 @@ on: paths: - .github/workflows/metrics.yml - dart/** - - flutter/** + - packages/flutter/** - metrics/** - "!**/*.md" branches-ignore: diff --git a/.github/workflows/min_version_test.yml b/.github/workflows/min_version_test.yml index 382ee6e839..4852d93911 100644 --- a/.github/workflows/min_version_test.yml +++ b/.github/workflows/min_version_test.yml @@ -10,7 +10,7 @@ on: - "!**/class-diagram.svg" - ".github/workflows/min_version_test.yml" - "dart/**" - - "flutter/**" + - "packages/flutter/**" - "min_version_test/**" jobs: diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index 2487c91212..5f8a5ec416 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -23,17 +23,17 @@ jobs: bundler-cache: true - name: flutter - working-directory: ./flutter/example + working-directory: ./packages/flutter/example run: | flutter pub get flutter build ios --no-codesign --obfuscate --split-debug-info=. - name: Install Fastlane - working-directory: ./flutter/example/ios + working-directory: ./packages/flutter/example/ios run: bundle install - name: Bump, Build & Upload App to TestFlight - working-directory: ./flutter/example/ios + working-directory: ./packages/flutter/example/ios env: APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} @@ -51,7 +51,7 @@ jobs: bundle exec fastlane upload_testflight - name: Upload Symbols to Sentry - working-directory: ./flutter/example + working-directory: ./packages/flutter/example env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} run: flutter packages pub run sentry_dart_plugin diff --git a/.github/workflows/update-deps.yml b/.github/workflows/update-deps.yml index c67638dee0..606dbcd794 100644 --- a/.github/workflows/update-deps.yml +++ b/.github/workflows/update-deps.yml @@ -13,7 +13,7 @@ jobs: android: uses: getsentry/github-workflows/.github/workflows/updater.yml@v2 with: - path: flutter/scripts/update-android.sh + path: packages/flutter/scripts/update-android.sh name: Android SDK secrets: api-token: ${{ secrets.CI_DEPLOY_KEY }} @@ -21,7 +21,7 @@ jobs: cocoa: uses: getsentry/github-workflows/.github/workflows/updater.yml@v2 with: - path: flutter/scripts/update-cocoa.sh + path: packages/flutter/scripts/update-cocoa.sh name: Cocoa SDK runs-on: macos-latest secrets: @@ -30,7 +30,7 @@ jobs: js: uses: getsentry/github-workflows/.github/workflows/updater.yml@v2 with: - path: flutter/scripts/update-js.sh + path: packages/flutter/scripts/update-js.sh name: JavaScript SDK secrets: api-token: ${{ secrets.CI_DEPLOY_KEY }} @@ -38,7 +38,7 @@ jobs: native: uses: getsentry/github-workflows/.github/workflows/updater.yml@v2 with: - path: flutter/scripts/update-native.sh + path: packages/flutter/scripts/update-native.sh name: Native SDK secrets: api-token: ${{ secrets.CI_DEPLOY_KEY }} diff --git a/.github/workflows/web-example-ghpages.yml b/.github/workflows/web-example-ghpages.yml index 1073c9747e..79ece0904b 100644 --- a/.github/workflows/web-example-ghpages.yml +++ b/.github/workflows/web-example-ghpages.yml @@ -17,13 +17,13 @@ jobs: - uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # pin@v2.21.0 - uses: bluefireteam/flutter-gh-pages@cf4a9312849577dbfd9df8f3d63d12ef6b09898e # pin@v9 with: - workingDir: flutter/example + workingDir: packages/flutter/example customArgs: --source-maps webRenderer: canvaskit baseHref: "/sentry-dart/" - name: Upload source maps run: | - cd flutter/example + cd packages/flutter/example flutter pub get dart run sentry_dart_plugin From 01e41330e453bbca356bab7f3bb89cc500a1f225 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 14:36:17 +0200 Subject: [PATCH 41/45] Update --- min_version_test/pubspec.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/min_version_test/pubspec.yaml b/min_version_test/pubspec.yaml index 791668d1b9..a410eefea7 100644 --- a/min_version_test/pubspec.yaml +++ b/min_version_test/pubspec.yaml @@ -46,11 +46,11 @@ dependency_overrides: sentry: path: ../packages/dart sentry_flutter: - path: ../flutter + path: ../packages/flutter sentry_dio: - path: ../dio + path: ../packages/dio sentry_logging: - path: ../logging + path: ../packages/logging # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 387477d9e9c7508719629fe9a5d733eac2c48210 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 14:42:57 +0200 Subject: [PATCH 42/45] Update --- .../flutter/example/pubspec_overrides.yaml | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/flutter/example/pubspec_overrides.yaml b/packages/flutter/example/pubspec_overrides.yaml index 7a00c71ed9..65ee86ab7c 100644 --- a/packages/flutter/example/pubspec_overrides.yaml +++ b/packages/flutter/example/pubspec_overrides.yaml @@ -2,15 +2,29 @@ dependency_overrides: sentry: path: ../../dart + sentry_flutter: + path: ../ sentry_dio: path: ../../dio - sentry_drift: - path: ../../drift + sentry_logging: + path: ../../logging + sentry_sqflite: + path: ../../../sqflite sentry_file: path: ../../file - sentry_flutter: - path: .. sentry_hive: path: ../../hive - sentry_logging: - path: ../../logging + sentry_drift: + path: ../../drift + sentry_isar: + path: ../../../isar + # isar community fork is more stable + isar: + version: ^3.1.0 + hosted: https://pub.isar-community.dev/ + isar_flutter_libs: + version: ^3.1.0 + hosted: https://pub.isar-community.dev/ + isar_generator: + version: ^3.1.0 + hosted: https://pub.isar-community.dev/ From 0e5657be262372b1174704106b2f78ff89af01fc Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 14:46:10 +0200 Subject: [PATCH 43/45] Update --- metrics/prepare.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metrics/prepare.sh b/metrics/prepare.sh index 66e62cde6f..6e33a460b2 100755 --- a/metrics/prepare.sh +++ b/metrics/prepare.sh @@ -28,7 +28,7 @@ dependency_overrides: sentry: path: ../../packages/dart sentry_flutter: - path: ../../flutter + path: ../../packages/flutter EOF From 433eb7825ca0cbcee699255dfcf378c4289e2c24 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 15:35:29 +0200 Subject: [PATCH 44/45] Update --- .github/workflows/isar.yml | 10 +++++----- isar/CHANGELOG.md | 1 - isar/dartdoc_options.yaml | 1 - isar/pubspec_overrides.yaml | 3 --- {isar => packages/isar}/.gitignore | 0 {isar => packages/isar}/.metadata | 0 packages/isar/CHANGELOG.md | 1 + {isar => packages/isar}/LICENSE | 0 {isar => packages/isar}/README.md | 0 {isar => packages/isar}/analysis_options.yaml | 0 packages/isar/dartdoc_options.yaml | 1 + {isar => packages/isar}/example/main.dart | 0 {isar => packages/isar}/example/user.dart | 0 {isar => packages/isar}/example/user.g.dart | 0 {isar => packages/isar}/lib/sentry_isar.dart | 0 {isar => packages/isar}/lib/src/sentry_isar.dart | 0 .../isar}/lib/src/sentry_isar_collection.dart | 0 .../isar}/lib/src/sentry_span_helper.dart | 0 {isar => packages/isar}/lib/src/version.dart | 0 {isar => packages/isar}/pubspec.yaml | 0 packages/isar/pubspec_overrides.yaml | 4 ++++ {isar => packages/isar}/test/mocks/mocks.dart | 0 {isar => packages/isar}/test/mocks/mocks.mocks.dart | 0 {isar => packages/isar}/test/person.dart | 0 {isar => packages/isar}/test/person.g.dart | 0 .../isar}/test/sentry_isar_collection_test.dart | 0 {isar => packages/isar}/test/sentry_isar_test.dart | 0 {isar => packages/isar}/test/utils.dart | 0 28 files changed, 11 insertions(+), 10 deletions(-) delete mode 120000 isar/CHANGELOG.md delete mode 120000 isar/dartdoc_options.yaml delete mode 100644 isar/pubspec_overrides.yaml rename {isar => packages/isar}/.gitignore (100%) rename {isar => packages/isar}/.metadata (100%) create mode 120000 packages/isar/CHANGELOG.md rename {isar => packages/isar}/LICENSE (100%) rename {isar => packages/isar}/README.md (100%) rename {isar => packages/isar}/analysis_options.yaml (100%) create mode 120000 packages/isar/dartdoc_options.yaml rename {isar => packages/isar}/example/main.dart (100%) rename {isar => packages/isar}/example/user.dart (100%) rename {isar => packages/isar}/example/user.g.dart (100%) rename {isar => packages/isar}/lib/sentry_isar.dart (100%) rename {isar => packages/isar}/lib/src/sentry_isar.dart (100%) rename {isar => packages/isar}/lib/src/sentry_isar_collection.dart (100%) rename {isar => packages/isar}/lib/src/sentry_span_helper.dart (100%) rename {isar => packages/isar}/lib/src/version.dart (100%) rename {isar => packages/isar}/pubspec.yaml (100%) create mode 100644 packages/isar/pubspec_overrides.yaml rename {isar => packages/isar}/test/mocks/mocks.dart (100%) rename {isar => packages/isar}/test/mocks/mocks.mocks.dart (100%) rename {isar => packages/isar}/test/person.dart (100%) rename {isar => packages/isar}/test/person.g.dart (100%) rename {isar => packages/isar}/test/sentry_isar_collection_test.dart (100%) rename {isar => packages/isar}/test/sentry_isar_test.dart (100%) rename {isar => packages/isar}/test/utils.dart (100%) diff --git a/.github/workflows/isar.yml b/.github/workflows/isar.yml index 857aa7832c..6771aa6497 100644 --- a/.github/workflows/isar.yml +++ b/.github/workflows/isar.yml @@ -12,8 +12,8 @@ on: - '.github/workflows/analyze.yml' - '.github/actions/flutter-test/**' - '.github/actions/coverage/**' - - 'dart/**' - - 'isar/**' + - 'packages/dart/**' + - 'packages/isar/**' jobs: cancel-previous-workflow: @@ -39,18 +39,18 @@ jobs: - uses: ./.github/actions/flutter-test with: - directory: isar + directory: packages/isar - uses: ./.github/actions/coverage if: matrix.target == 'linux' && matrix.sdk == 'stable' with: token: ${{ secrets.CODECOV_TOKEN }} - directory: isar + directory: packages/isar coverage: sentry_isar min-coverage: 55 analyze: uses: ./.github/workflows/analyze.yml with: - package: isar + package: packages/isar sdk: flutter diff --git a/isar/CHANGELOG.md b/isar/CHANGELOG.md deleted file mode 120000 index 04c99a55ca..0000000000 --- a/isar/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../CHANGELOG.md \ No newline at end of file diff --git a/isar/dartdoc_options.yaml b/isar/dartdoc_options.yaml deleted file mode 120000 index fcb0f9cc26..0000000000 --- a/isar/dartdoc_options.yaml +++ /dev/null @@ -1 +0,0 @@ -../packages/dart/dartdoc_options.yaml \ No newline at end of file diff --git a/isar/pubspec_overrides.yaml b/isar/pubspec_overrides.yaml deleted file mode 100644 index 0a17afda20..0000000000 --- a/isar/pubspec_overrides.yaml +++ /dev/null @@ -1,3 +0,0 @@ -dependency_overrides: - sentry: - path: ../packages/dart diff --git a/isar/.gitignore b/packages/isar/.gitignore similarity index 100% rename from isar/.gitignore rename to packages/isar/.gitignore diff --git a/isar/.metadata b/packages/isar/.metadata similarity index 100% rename from isar/.metadata rename to packages/isar/.metadata diff --git a/packages/isar/CHANGELOG.md b/packages/isar/CHANGELOG.md new file mode 120000 index 0000000000..699cc9e7b7 --- /dev/null +++ b/packages/isar/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/isar/LICENSE b/packages/isar/LICENSE similarity index 100% rename from isar/LICENSE rename to packages/isar/LICENSE diff --git a/isar/README.md b/packages/isar/README.md similarity index 100% rename from isar/README.md rename to packages/isar/README.md diff --git a/isar/analysis_options.yaml b/packages/isar/analysis_options.yaml similarity index 100% rename from isar/analysis_options.yaml rename to packages/isar/analysis_options.yaml diff --git a/packages/isar/dartdoc_options.yaml b/packages/isar/dartdoc_options.yaml new file mode 120000 index 0000000000..7cbb8c0d74 --- /dev/null +++ b/packages/isar/dartdoc_options.yaml @@ -0,0 +1 @@ +../dart/dartdoc_options.yaml \ No newline at end of file diff --git a/isar/example/main.dart b/packages/isar/example/main.dart similarity index 100% rename from isar/example/main.dart rename to packages/isar/example/main.dart diff --git a/isar/example/user.dart b/packages/isar/example/user.dart similarity index 100% rename from isar/example/user.dart rename to packages/isar/example/user.dart diff --git a/isar/example/user.g.dart b/packages/isar/example/user.g.dart similarity index 100% rename from isar/example/user.g.dart rename to packages/isar/example/user.g.dart diff --git a/isar/lib/sentry_isar.dart b/packages/isar/lib/sentry_isar.dart similarity index 100% rename from isar/lib/sentry_isar.dart rename to packages/isar/lib/sentry_isar.dart diff --git a/isar/lib/src/sentry_isar.dart b/packages/isar/lib/src/sentry_isar.dart similarity index 100% rename from isar/lib/src/sentry_isar.dart rename to packages/isar/lib/src/sentry_isar.dart diff --git a/isar/lib/src/sentry_isar_collection.dart b/packages/isar/lib/src/sentry_isar_collection.dart similarity index 100% rename from isar/lib/src/sentry_isar_collection.dart rename to packages/isar/lib/src/sentry_isar_collection.dart diff --git a/isar/lib/src/sentry_span_helper.dart b/packages/isar/lib/src/sentry_span_helper.dart similarity index 100% rename from isar/lib/src/sentry_span_helper.dart rename to packages/isar/lib/src/sentry_span_helper.dart diff --git a/isar/lib/src/version.dart b/packages/isar/lib/src/version.dart similarity index 100% rename from isar/lib/src/version.dart rename to packages/isar/lib/src/version.dart diff --git a/isar/pubspec.yaml b/packages/isar/pubspec.yaml similarity index 100% rename from isar/pubspec.yaml rename to packages/isar/pubspec.yaml diff --git a/packages/isar/pubspec_overrides.yaml b/packages/isar/pubspec_overrides.yaml new file mode 100644 index 0000000000..04ac16fe75 --- /dev/null +++ b/packages/isar/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# melos_managed_dependency_overrides: sentry +dependency_overrides: + sentry: + path: ../dart diff --git a/isar/test/mocks/mocks.dart b/packages/isar/test/mocks/mocks.dart similarity index 100% rename from isar/test/mocks/mocks.dart rename to packages/isar/test/mocks/mocks.dart diff --git a/isar/test/mocks/mocks.mocks.dart b/packages/isar/test/mocks/mocks.mocks.dart similarity index 100% rename from isar/test/mocks/mocks.mocks.dart rename to packages/isar/test/mocks/mocks.mocks.dart diff --git a/isar/test/person.dart b/packages/isar/test/person.dart similarity index 100% rename from isar/test/person.dart rename to packages/isar/test/person.dart diff --git a/isar/test/person.g.dart b/packages/isar/test/person.g.dart similarity index 100% rename from isar/test/person.g.dart rename to packages/isar/test/person.g.dart diff --git a/isar/test/sentry_isar_collection_test.dart b/packages/isar/test/sentry_isar_collection_test.dart similarity index 100% rename from isar/test/sentry_isar_collection_test.dart rename to packages/isar/test/sentry_isar_collection_test.dart diff --git a/isar/test/sentry_isar_test.dart b/packages/isar/test/sentry_isar_test.dart similarity index 100% rename from isar/test/sentry_isar_test.dart rename to packages/isar/test/sentry_isar_test.dart diff --git a/isar/test/utils.dart b/packages/isar/test/utils.dart similarity index 100% rename from isar/test/utils.dart rename to packages/isar/test/utils.dart From 070f3ce033e29446baac64b81151c251f46416e7 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 5 Aug 2025 15:38:20 +0200 Subject: [PATCH 45/45] Update --- packages/flutter/example/pubspec_overrides.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/example/pubspec_overrides.yaml b/packages/flutter/example/pubspec_overrides.yaml index 65ee86ab7c..48859e298c 100644 --- a/packages/flutter/example/pubspec_overrides.yaml +++ b/packages/flutter/example/pubspec_overrides.yaml @@ -17,7 +17,7 @@ dependency_overrides: sentry_drift: path: ../../drift sentry_isar: - path: ../../../isar + path: ../../isar # isar community fork is more stable isar: version: ^3.1.0