aeri-python 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aeri/__init__.py +72 -0
- aeri/_client/_validation.py +204 -0
- aeri/_client/attributes.py +188 -0
- aeri/_client/client.py +3761 -0
- aeri/_client/constants.py +65 -0
- aeri/_client/datasets.py +302 -0
- aeri/_client/environment_variables.py +158 -0
- aeri/_client/get_client.py +149 -0
- aeri/_client/observe.py +661 -0
- aeri/_client/propagation.py +475 -0
- aeri/_client/resource_manager.py +510 -0
- aeri/_client/span.py +1519 -0
- aeri/_client/span_filter.py +76 -0
- aeri/_client/span_processor.py +206 -0
- aeri/_client/utils.py +132 -0
- aeri/_task_manager/media_manager.py +331 -0
- aeri/_task_manager/media_upload_consumer.py +44 -0
- aeri/_task_manager/media_upload_queue.py +12 -0
- aeri/_task_manager/score_ingestion_consumer.py +208 -0
- aeri/_task_manager/task_manager.py +475 -0
- aeri/_utils/__init__.py +19 -0
- aeri/_utils/environment.py +34 -0
- aeri/_utils/error_logging.py +47 -0
- aeri/_utils/parse_error.py +99 -0
- aeri/_utils/prompt_cache.py +188 -0
- aeri/_utils/request.py +137 -0
- aeri/_utils/serializer.py +205 -0
- aeri/api/.fern/metadata.json +14 -0
- aeri/api/__init__.py +836 -0
- aeri/api/annotation_queues/__init__.py +82 -0
- aeri/api/annotation_queues/client.py +1111 -0
- aeri/api/annotation_queues/raw_client.py +2288 -0
- aeri/api/annotation_queues/types/__init__.py +84 -0
- aeri/api/annotation_queues/types/annotation_queue.py +28 -0
- aeri/api/annotation_queues/types/annotation_queue_assignment_request.py +16 -0
- aeri/api/annotation_queues/types/annotation_queue_item.py +34 -0
- aeri/api/annotation_queues/types/annotation_queue_object_type.py +26 -0
- aeri/api/annotation_queues/types/annotation_queue_status.py +22 -0
- aeri/api/annotation_queues/types/create_annotation_queue_assignment_response.py +18 -0
- aeri/api/annotation_queues/types/create_annotation_queue_item_request.py +25 -0
- aeri/api/annotation_queues/types/create_annotation_queue_request.py +20 -0
- aeri/api/annotation_queues/types/delete_annotation_queue_assignment_response.py +14 -0
- aeri/api/annotation_queues/types/delete_annotation_queue_item_response.py +15 -0
- aeri/api/annotation_queues/types/paginated_annotation_queue_items.py +17 -0
- aeri/api/annotation_queues/types/paginated_annotation_queues.py +17 -0
- aeri/api/annotation_queues/types/update_annotation_queue_item_request.py +15 -0
- aeri/api/blob_storage_integrations/__init__.py +73 -0
- aeri/api/blob_storage_integrations/client.py +550 -0
- aeri/api/blob_storage_integrations/raw_client.py +976 -0
- aeri/api/blob_storage_integrations/types/__init__.py +77 -0
- aeri/api/blob_storage_integrations/types/blob_storage_export_frequency.py +26 -0
- aeri/api/blob_storage_integrations/types/blob_storage_export_mode.py +26 -0
- aeri/api/blob_storage_integrations/types/blob_storage_integration_deletion_response.py +14 -0
- aeri/api/blob_storage_integrations/types/blob_storage_integration_file_type.py +26 -0
- aeri/api/blob_storage_integrations/types/blob_storage_integration_response.py +64 -0
- aeri/api/blob_storage_integrations/types/blob_storage_integration_status_response.py +50 -0
- aeri/api/blob_storage_integrations/types/blob_storage_integration_type.py +26 -0
- aeri/api/blob_storage_integrations/types/blob_storage_integrations_response.py +15 -0
- aeri/api/blob_storage_integrations/types/blob_storage_sync_status.py +47 -0
- aeri/api/blob_storage_integrations/types/create_blob_storage_integration_request.py +91 -0
- aeri/api/client.py +679 -0
- aeri/api/comments/__init__.py +44 -0
- aeri/api/comments/client.py +407 -0
- aeri/api/comments/raw_client.py +750 -0
- aeri/api/comments/types/__init__.py +46 -0
- aeri/api/comments/types/create_comment_request.py +47 -0
- aeri/api/comments/types/create_comment_response.py +17 -0
- aeri/api/comments/types/get_comments_response.py +17 -0
- aeri/api/commons/__init__.py +210 -0
- aeri/api/commons/errors/__init__.py +56 -0
- aeri/api/commons/errors/access_denied_error.py +12 -0
- aeri/api/commons/errors/error.py +12 -0
- aeri/api/commons/errors/method_not_allowed_error.py +12 -0
- aeri/api/commons/errors/not_found_error.py +12 -0
- aeri/api/commons/errors/unauthorized_error.py +12 -0
- aeri/api/commons/types/__init__.py +190 -0
- aeri/api/commons/types/base_score.py +90 -0
- aeri/api/commons/types/base_score_v1.py +70 -0
- aeri/api/commons/types/boolean_score.py +26 -0
- aeri/api/commons/types/boolean_score_v1.py +26 -0
- aeri/api/commons/types/categorical_score.py +26 -0
- aeri/api/commons/types/categorical_score_v1.py +26 -0
- aeri/api/commons/types/comment.py +36 -0
- aeri/api/commons/types/comment_object_type.py +30 -0
- aeri/api/commons/types/config_category.py +15 -0
- aeri/api/commons/types/correction_score.py +26 -0
- aeri/api/commons/types/create_score_value.py +5 -0
- aeri/api/commons/types/dataset.py +49 -0
- aeri/api/commons/types/dataset_item.py +58 -0
- aeri/api/commons/types/dataset_run.py +63 -0
- aeri/api/commons/types/dataset_run_item.py +40 -0
- aeri/api/commons/types/dataset_run_with_items.py +19 -0
- aeri/api/commons/types/dataset_status.py +22 -0
- aeri/api/commons/types/map_value.py +11 -0
- aeri/api/commons/types/model.py +125 -0
- aeri/api/commons/types/model_price.py +14 -0
- aeri/api/commons/types/model_usage_unit.py +42 -0
- aeri/api/commons/types/numeric_score.py +17 -0
- aeri/api/commons/types/numeric_score_v1.py +17 -0
- aeri/api/commons/types/observation.py +142 -0
- aeri/api/commons/types/observation_level.py +30 -0
- aeri/api/commons/types/observation_v2.py +235 -0
- aeri/api/commons/types/observations_view.py +89 -0
- aeri/api/commons/types/pricing_tier.py +91 -0
- aeri/api/commons/types/pricing_tier_condition.py +68 -0
- aeri/api/commons/types/pricing_tier_input.py +76 -0
- aeri/api/commons/types/pricing_tier_operator.py +42 -0
- aeri/api/commons/types/score.py +201 -0
- aeri/api/commons/types/score_config.py +66 -0
- aeri/api/commons/types/score_config_data_type.py +26 -0
- aeri/api/commons/types/score_data_type.py +30 -0
- aeri/api/commons/types/score_source.py +26 -0
- aeri/api/commons/types/score_v1.py +131 -0
- aeri/api/commons/types/session.py +25 -0
- aeri/api/commons/types/session_with_traces.py +15 -0
- aeri/api/commons/types/trace.py +84 -0
- aeri/api/commons/types/trace_with_details.py +43 -0
- aeri/api/commons/types/trace_with_full_details.py +45 -0
- aeri/api/commons/types/usage.py +59 -0
- aeri/api/core/__init__.py +111 -0
- aeri/api/core/api_error.py +23 -0
- aeri/api/core/client_wrapper.py +141 -0
- aeri/api/core/datetime_utils.py +30 -0
- aeri/api/core/enum.py +20 -0
- aeri/api/core/file.py +70 -0
- aeri/api/core/force_multipart.py +18 -0
- aeri/api/core/http_client.py +711 -0
- aeri/api/core/http_response.py +55 -0
- aeri/api/core/http_sse/__init__.py +48 -0
- aeri/api/core/http_sse/_api.py +114 -0
- aeri/api/core/http_sse/_decoders.py +66 -0
- aeri/api/core/http_sse/_exceptions.py +7 -0
- aeri/api/core/http_sse/_models.py +17 -0
- aeri/api/core/jsonable_encoder.py +102 -0
- aeri/api/core/pydantic_utilities.py +310 -0
- aeri/api/core/query_encoder.py +60 -0
- aeri/api/core/remove_none_from_dict.py +11 -0
- aeri/api/core/request_options.py +35 -0
- aeri/api/core/serialization.py +282 -0
- aeri/api/dataset_items/__init__.py +52 -0
- aeri/api/dataset_items/client.py +499 -0
- aeri/api/dataset_items/raw_client.py +973 -0
- aeri/api/dataset_items/types/__init__.py +50 -0
- aeri/api/dataset_items/types/create_dataset_item_request.py +37 -0
- aeri/api/dataset_items/types/delete_dataset_item_response.py +17 -0
- aeri/api/dataset_items/types/paginated_dataset_items.py +17 -0
- aeri/api/dataset_run_items/__init__.py +43 -0
- aeri/api/dataset_run_items/client.py +323 -0
- aeri/api/dataset_run_items/raw_client.py +547 -0
- aeri/api/dataset_run_items/types/__init__.py +44 -0
- aeri/api/dataset_run_items/types/create_dataset_run_item_request.py +51 -0
- aeri/api/dataset_run_items/types/paginated_dataset_run_items.py +17 -0
- aeri/api/datasets/__init__.py +55 -0
- aeri/api/datasets/client.py +661 -0
- aeri/api/datasets/raw_client.py +1368 -0
- aeri/api/datasets/types/__init__.py +53 -0
- aeri/api/datasets/types/create_dataset_request.py +31 -0
- aeri/api/datasets/types/delete_dataset_run_response.py +14 -0
- aeri/api/datasets/types/paginated_dataset_runs.py +17 -0
- aeri/api/datasets/types/paginated_datasets.py +17 -0
- aeri/api/health/__init__.py +44 -0
- aeri/api/health/client.py +112 -0
- aeri/api/health/errors/__init__.py +42 -0
- aeri/api/health/errors/service_unavailable_error.py +13 -0
- aeri/api/health/raw_client.py +227 -0
- aeri/api/health/types/__init__.py +40 -0
- aeri/api/health/types/health_response.py +30 -0
- aeri/api/ingestion/__init__.py +169 -0
- aeri/api/ingestion/client.py +221 -0
- aeri/api/ingestion/raw_client.py +293 -0
- aeri/api/ingestion/types/__init__.py +169 -0
- aeri/api/ingestion/types/base_event.py +27 -0
- aeri/api/ingestion/types/create_event_body.py +14 -0
- aeri/api/ingestion/types/create_event_event.py +15 -0
- aeri/api/ingestion/types/create_generation_body.py +40 -0
- aeri/api/ingestion/types/create_generation_event.py +15 -0
- aeri/api/ingestion/types/create_observation_event.py +15 -0
- aeri/api/ingestion/types/create_span_body.py +19 -0
- aeri/api/ingestion/types/create_span_event.py +15 -0
- aeri/api/ingestion/types/ingestion_error.py +17 -0
- aeri/api/ingestion/types/ingestion_event.py +155 -0
- aeri/api/ingestion/types/ingestion_response.py +17 -0
- aeri/api/ingestion/types/ingestion_success.py +15 -0
- aeri/api/ingestion/types/ingestion_usage.py +8 -0
- aeri/api/ingestion/types/observation_body.py +53 -0
- aeri/api/ingestion/types/observation_type.py +54 -0
- aeri/api/ingestion/types/open_ai_completion_usage_schema.py +26 -0
- aeri/api/ingestion/types/open_ai_response_usage_schema.py +24 -0
- aeri/api/ingestion/types/open_ai_usage.py +28 -0
- aeri/api/ingestion/types/optional_observation_body.py +36 -0
- aeri/api/ingestion/types/score_body.py +75 -0
- aeri/api/ingestion/types/score_event.py +15 -0
- aeri/api/ingestion/types/sdk_log_body.py +14 -0
- aeri/api/ingestion/types/sdk_log_event.py +15 -0
- aeri/api/ingestion/types/trace_body.py +36 -0
- aeri/api/ingestion/types/trace_event.py +15 -0
- aeri/api/ingestion/types/update_event_body.py +14 -0
- aeri/api/ingestion/types/update_generation_body.py +40 -0
- aeri/api/ingestion/types/update_generation_event.py +15 -0
- aeri/api/ingestion/types/update_observation_event.py +15 -0
- aeri/api/ingestion/types/update_span_body.py +19 -0
- aeri/api/ingestion/types/update_span_event.py +15 -0
- aeri/api/ingestion/types/usage_details.py +10 -0
- aeri/api/legacy/__init__.py +61 -0
- aeri/api/legacy/client.py +105 -0
- aeri/api/legacy/metrics_v1/__init__.py +40 -0
- aeri/api/legacy/metrics_v1/client.py +214 -0
- aeri/api/legacy/metrics_v1/raw_client.py +322 -0
- aeri/api/legacy/metrics_v1/types/__init__.py +40 -0
- aeri/api/legacy/metrics_v1/types/metrics_response.py +19 -0
- aeri/api/legacy/observations_v1/__init__.py +43 -0
- aeri/api/legacy/observations_v1/client.py +523 -0
- aeri/api/legacy/observations_v1/raw_client.py +759 -0
- aeri/api/legacy/observations_v1/types/__init__.py +44 -0
- aeri/api/legacy/observations_v1/types/observations.py +17 -0
- aeri/api/legacy/observations_v1/types/observations_views.py +17 -0
- aeri/api/legacy/raw_client.py +13 -0
- aeri/api/legacy/score_v1/__init__.py +43 -0
- aeri/api/legacy/score_v1/client.py +329 -0
- aeri/api/legacy/score_v1/raw_client.py +545 -0
- aeri/api/legacy/score_v1/types/__init__.py +44 -0
- aeri/api/legacy/score_v1/types/create_score_request.py +75 -0
- aeri/api/legacy/score_v1/types/create_score_response.py +17 -0
- aeri/api/llm_connections/__init__.py +55 -0
- aeri/api/llm_connections/client.py +311 -0
- aeri/api/llm_connections/raw_client.py +541 -0
- aeri/api/llm_connections/types/__init__.py +53 -0
- aeri/api/llm_connections/types/llm_adapter.py +38 -0
- aeri/api/llm_connections/types/llm_connection.py +77 -0
- aeri/api/llm_connections/types/paginated_llm_connections.py +17 -0
- aeri/api/llm_connections/types/upsert_llm_connection_request.py +69 -0
- aeri/api/media/__init__.py +58 -0
- aeri/api/media/client.py +427 -0
- aeri/api/media/raw_client.py +739 -0
- aeri/api/media/types/__init__.py +56 -0
- aeri/api/media/types/get_media_response.py +55 -0
- aeri/api/media/types/get_media_upload_url_request.py +51 -0
- aeri/api/media/types/get_media_upload_url_response.py +28 -0
- aeri/api/media/types/media_content_type.py +232 -0
- aeri/api/media/types/patch_media_body.py +43 -0
- aeri/api/metrics/__init__.py +40 -0
- aeri/api/metrics/client.py +422 -0
- aeri/api/metrics/raw_client.py +530 -0
- aeri/api/metrics/types/__init__.py +40 -0
- aeri/api/metrics/types/metrics_v2response.py +19 -0
- aeri/api/models/__init__.py +43 -0
- aeri/api/models/client.py +523 -0
- aeri/api/models/raw_client.py +993 -0
- aeri/api/models/types/__init__.py +44 -0
- aeri/api/models/types/create_model_request.py +103 -0
- aeri/api/models/types/paginated_models.py +17 -0
- aeri/api/observations/__init__.py +43 -0
- aeri/api/observations/client.py +522 -0
- aeri/api/observations/raw_client.py +641 -0
- aeri/api/observations/types/__init__.py +44 -0
- aeri/api/observations/types/observations_v2meta.py +21 -0
- aeri/api/observations/types/observations_v2response.py +28 -0
- aeri/api/opentelemetry/__init__.py +67 -0
- aeri/api/opentelemetry/client.py +276 -0
- aeri/api/opentelemetry/raw_client.py +291 -0
- aeri/api/opentelemetry/types/__init__.py +65 -0
- aeri/api/opentelemetry/types/otel_attribute.py +27 -0
- aeri/api/opentelemetry/types/otel_attribute_value.py +46 -0
- aeri/api/opentelemetry/types/otel_resource.py +24 -0
- aeri/api/opentelemetry/types/otel_resource_span.py +32 -0
- aeri/api/opentelemetry/types/otel_scope.py +34 -0
- aeri/api/opentelemetry/types/otel_scope_span.py +28 -0
- aeri/api/opentelemetry/types/otel_span.py +76 -0
- aeri/api/opentelemetry/types/otel_trace_response.py +16 -0
- aeri/api/organizations/__init__.py +73 -0
- aeri/api/organizations/client.py +756 -0
- aeri/api/organizations/raw_client.py +1707 -0
- aeri/api/organizations/types/__init__.py +71 -0
- aeri/api/organizations/types/delete_membership_request.py +16 -0
- aeri/api/organizations/types/membership_deletion_response.py +17 -0
- aeri/api/organizations/types/membership_request.py +18 -0
- aeri/api/organizations/types/membership_response.py +20 -0
- aeri/api/organizations/types/membership_role.py +30 -0
- aeri/api/organizations/types/memberships_response.py +15 -0
- aeri/api/organizations/types/organization_api_key.py +31 -0
- aeri/api/organizations/types/organization_api_keys_response.py +19 -0
- aeri/api/organizations/types/organization_project.py +25 -0
- aeri/api/organizations/types/organization_projects_response.py +15 -0
- aeri/api/projects/__init__.py +67 -0
- aeri/api/projects/client.py +760 -0
- aeri/api/projects/raw_client.py +1577 -0
- aeri/api/projects/types/__init__.py +65 -0
- aeri/api/projects/types/api_key_deletion_response.py +18 -0
- aeri/api/projects/types/api_key_list.py +23 -0
- aeri/api/projects/types/api_key_response.py +30 -0
- aeri/api/projects/types/api_key_summary.py +35 -0
- aeri/api/projects/types/organization.py +22 -0
- aeri/api/projects/types/project.py +34 -0
- aeri/api/projects/types/project_deletion_response.py +15 -0
- aeri/api/projects/types/projects.py +15 -0
- aeri/api/prompt_version/__init__.py +4 -0
- aeri/api/prompt_version/client.py +157 -0
- aeri/api/prompt_version/raw_client.py +264 -0
- aeri/api/prompts/__init__.py +100 -0
- aeri/api/prompts/client.py +550 -0
- aeri/api/prompts/raw_client.py +987 -0
- aeri/api/prompts/types/__init__.py +96 -0
- aeri/api/prompts/types/base_prompt.py +42 -0
- aeri/api/prompts/types/chat_message.py +17 -0
- aeri/api/prompts/types/chat_message_type.py +15 -0
- aeri/api/prompts/types/chat_message_with_placeholders.py +8 -0
- aeri/api/prompts/types/chat_prompt.py +15 -0
- aeri/api/prompts/types/create_chat_prompt_request.py +37 -0
- aeri/api/prompts/types/create_chat_prompt_type.py +15 -0
- aeri/api/prompts/types/create_prompt_request.py +8 -0
- aeri/api/prompts/types/create_text_prompt_request.py +36 -0
- aeri/api/prompts/types/create_text_prompt_type.py +15 -0
- aeri/api/prompts/types/placeholder_message.py +16 -0
- aeri/api/prompts/types/placeholder_message_type.py +15 -0
- aeri/api/prompts/types/prompt.py +58 -0
- aeri/api/prompts/types/prompt_meta.py +35 -0
- aeri/api/prompts/types/prompt_meta_list_response.py +17 -0
- aeri/api/prompts/types/prompt_type.py +20 -0
- aeri/api/prompts/types/text_prompt.py +14 -0
- aeri/api/scim/__init__.py +94 -0
- aeri/api/scim/client.py +686 -0
- aeri/api/scim/raw_client.py +1528 -0
- aeri/api/scim/types/__init__.py +92 -0
- aeri/api/scim/types/authentication_scheme.py +20 -0
- aeri/api/scim/types/bulk_config.py +22 -0
- aeri/api/scim/types/empty_response.py +16 -0
- aeri/api/scim/types/filter_config.py +17 -0
- aeri/api/scim/types/resource_meta.py +17 -0
- aeri/api/scim/types/resource_type.py +27 -0
- aeri/api/scim/types/resource_types_response.py +21 -0
- aeri/api/scim/types/schema_extension.py +17 -0
- aeri/api/scim/types/schema_resource.py +19 -0
- aeri/api/scim/types/schemas_response.py +21 -0
- aeri/api/scim/types/scim_email.py +16 -0
- aeri/api/scim/types/scim_feature_support.py +14 -0
- aeri/api/scim/types/scim_name.py +14 -0
- aeri/api/scim/types/scim_user.py +24 -0
- aeri/api/scim/types/scim_users_list_response.py +25 -0
- aeri/api/scim/types/service_provider_config.py +36 -0
- aeri/api/scim/types/user_meta.py +20 -0
- aeri/api/score_configs/__init__.py +44 -0
- aeri/api/score_configs/client.py +526 -0
- aeri/api/score_configs/raw_client.py +1012 -0
- aeri/api/score_configs/types/__init__.py +46 -0
- aeri/api/score_configs/types/create_score_config_request.py +46 -0
- aeri/api/score_configs/types/score_configs.py +17 -0
- aeri/api/score_configs/types/update_score_config_request.py +53 -0
- aeri/api/scores/__init__.py +76 -0
- aeri/api/scores/client.py +420 -0
- aeri/api/scores/raw_client.py +656 -0
- aeri/api/scores/types/__init__.py +76 -0
- aeri/api/scores/types/get_scores_response.py +17 -0
- aeri/api/scores/types/get_scores_response_data.py +211 -0
- aeri/api/scores/types/get_scores_response_data_boolean.py +15 -0
- aeri/api/scores/types/get_scores_response_data_categorical.py +15 -0
- aeri/api/scores/types/get_scores_response_data_correction.py +15 -0
- aeri/api/scores/types/get_scores_response_data_numeric.py +15 -0
- aeri/api/scores/types/get_scores_response_trace_data.py +38 -0
- aeri/api/sessions/__init__.py +40 -0
- aeri/api/sessions/client.py +262 -0
- aeri/api/sessions/raw_client.py +500 -0
- aeri/api/sessions/types/__init__.py +40 -0
- aeri/api/sessions/types/paginated_sessions.py +17 -0
- aeri/api/trace/__init__.py +44 -0
- aeri/api/trace/client.py +728 -0
- aeri/api/trace/raw_client.py +1208 -0
- aeri/api/trace/types/__init__.py +46 -0
- aeri/api/trace/types/delete_trace_response.py +14 -0
- aeri/api/trace/types/sort.py +14 -0
- aeri/api/trace/types/traces.py +17 -0
- aeri/api/utils/__init__.py +44 -0
- aeri/api/utils/pagination/__init__.py +40 -0
- aeri/api/utils/pagination/types/__init__.py +40 -0
- aeri/api/utils/pagination/types/meta_response.py +38 -0
- aeri/batch_evaluation.py +1643 -0
- aeri/experiment.py +1044 -0
- aeri/langchain/CallbackHandler.py +1377 -0
- aeri/langchain/__init__.py +5 -0
- aeri/langchain/utils.py +212 -0
- aeri/logger.py +28 -0
- aeri/media.py +352 -0
- aeri/model.py +477 -0
- aeri/openai.py +1124 -0
- aeri/py.typed +0 -0
- aeri/span_filter.py +17 -0
- aeri/types.py +79 -0
- aeri/version.py +3 -0
- aeri_python-4.0.0.dist-info/METADATA +51 -0
- aeri_python-4.0.0.dist-info/RECORD +391 -0
- aeri_python-4.0.0.dist-info/WHEEL +4 -0
- aeri_python-4.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
"""Tracer implementation for Aeri OpenTelemetry integration.
|
|
2
|
+
|
|
3
|
+
This module provides the AeriTracer class, a thread-safe singleton that manages OpenTelemetry
|
|
4
|
+
tracing infrastructure for Aeri. It handles tracer initialization, span processors,
|
|
5
|
+
API clients, and coordinates background tasks for efficient data processing and media handling.
|
|
6
|
+
|
|
7
|
+
Key features:
|
|
8
|
+
- Thread-safe OpenTelemetry tracer with Aeri-specific span processors and sampling
|
|
9
|
+
- Configurable batch processing of spans and scores with intelligent flushing behavior
|
|
10
|
+
- Asynchronous background media upload processing with dedicated worker threads
|
|
11
|
+
- Concurrent score ingestion with batching and retry mechanisms
|
|
12
|
+
- Automatic project ID discovery and caching
|
|
13
|
+
- Graceful shutdown handling with proper resource cleanup
|
|
14
|
+
- Fault tolerance with detailed error logging and recovery mechanisms
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import atexit
|
|
18
|
+
import os
|
|
19
|
+
import threading
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from queue import Full, Queue
|
|
22
|
+
from typing import Any, Callable, Dict, List, Literal, Optional, Union, cast
|
|
23
|
+
|
|
24
|
+
import httpx
|
|
25
|
+
from opentelemetry import trace as otel_trace_api
|
|
26
|
+
from opentelemetry.sdk.resources import Resource
|
|
27
|
+
from opentelemetry.sdk.trace import ReadableSpan, TracerProvider
|
|
28
|
+
from opentelemetry.sdk.trace.sampling import Decision, TraceIdRatioBased
|
|
29
|
+
from opentelemetry.trace import Tracer
|
|
30
|
+
from pydantic import BaseModel, ConfigDict, ValidationError
|
|
31
|
+
|
|
32
|
+
from aeri._client.attributes import AeriOtelSpanAttributes
|
|
33
|
+
from aeri._client.constants import AERI_TRACER_NAME
|
|
34
|
+
from aeri._client.environment_variables import (
|
|
35
|
+
AERI_MEDIA_UPLOAD_ENABLED,
|
|
36
|
+
AERI_MEDIA_UPLOAD_THREAD_COUNT,
|
|
37
|
+
AERI_RELEASE,
|
|
38
|
+
AERI_TRACING_ENVIRONMENT,
|
|
39
|
+
)
|
|
40
|
+
from aeri._client.span_processor import AeriSpanProcessor
|
|
41
|
+
from aeri._task_manager.media_manager import MediaManager
|
|
42
|
+
from aeri._task_manager.media_upload_consumer import MediaUploadConsumer
|
|
43
|
+
from aeri._task_manager.task_manager import AsyncIngestionTaskManager
|
|
44
|
+
from aeri._utils.environment import get_common_release_envs
|
|
45
|
+
from aeri._utils.prompt_cache import PromptCache
|
|
46
|
+
from aeri.api import AsyncAeriAPI, AeriAPI
|
|
47
|
+
from aeri.api.ingestion.types.ingestion_event import (
|
|
48
|
+
IngestionEvent_ScoreCreate,
|
|
49
|
+
IngestionEvent_TraceCreate,
|
|
50
|
+
)
|
|
51
|
+
from aeri.api.ingestion.types.score_body import ScoreBody
|
|
52
|
+
from aeri.api.ingestion.types.trace_body import TraceBody
|
|
53
|
+
from aeri.logger import aeri_logger
|
|
54
|
+
from aeri.types import MaskFunction
|
|
55
|
+
|
|
56
|
+
from ..version import __version__ as aeri_version
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class _StrictScoreQueueEvent(BaseModel):
|
|
60
|
+
model_config = ConfigDict(extra="forbid", strict=True)
|
|
61
|
+
|
|
62
|
+
id: str
|
|
63
|
+
type: Literal["score-create"]
|
|
64
|
+
timestamp: datetime
|
|
65
|
+
body: ScoreBody
|
|
66
|
+
metadata: Optional[Any] = None
|
|
67
|
+
|
|
68
|
+
def to_ingestion_event(self) -> IngestionEvent_ScoreCreate:
|
|
69
|
+
return IngestionEvent_ScoreCreate(
|
|
70
|
+
id=self.id,
|
|
71
|
+
type=self.type,
|
|
72
|
+
timestamp=self.timestamp.isoformat(),
|
|
73
|
+
body=self.body,
|
|
74
|
+
metadata=self.metadata,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class _StrictTraceQueueEvent(BaseModel):
|
|
79
|
+
model_config = ConfigDict(extra="forbid", strict=True)
|
|
80
|
+
|
|
81
|
+
id: str
|
|
82
|
+
type: Literal["trace-create"]
|
|
83
|
+
timestamp: datetime
|
|
84
|
+
body: TraceBody
|
|
85
|
+
metadata: Optional[Any] = None
|
|
86
|
+
|
|
87
|
+
def to_ingestion_event(self) -> IngestionEvent_TraceCreate:
|
|
88
|
+
return IngestionEvent_TraceCreate(
|
|
89
|
+
id=self.id,
|
|
90
|
+
type=self.type,
|
|
91
|
+
timestamp=self.timestamp.isoformat(),
|
|
92
|
+
body=self.body,
|
|
93
|
+
metadata=self.metadata,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
IngestionQueueEvent = Union[IngestionEvent_ScoreCreate, IngestionEvent_TraceCreate]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class AeriResourceManager:
|
|
101
|
+
"""Thread-safe singleton that provides access to the OpenTelemetry tracer and processors.
|
|
102
|
+
|
|
103
|
+
This class implements a thread-safe singleton pattern keyed by the public API key,
|
|
104
|
+
ensuring that only one tracer instance exists per API key combination. It manages
|
|
105
|
+
the lifecycle of the OpenTelemetry tracer provider, span processors, and resource
|
|
106
|
+
attributes, as well as background threads for media uploads and score ingestion.
|
|
107
|
+
|
|
108
|
+
The tracer is responsible for:
|
|
109
|
+
1. Setting up the OpenTelemetry tracer with appropriate sampling and configuration
|
|
110
|
+
2. Managing the span processor for exporting spans to the Aeri API
|
|
111
|
+
3. Creating and managing Aeri API clients (both synchronous and asynchronous)
|
|
112
|
+
4. Handling background media upload processing via dedicated worker threads
|
|
113
|
+
5. Processing and batching score ingestion events with configurable flush settings
|
|
114
|
+
6. Retrieving and caching project information for URL generation and media handling
|
|
115
|
+
7. Coordinating graceful shutdown of all background processes with proper resource cleanup
|
|
116
|
+
|
|
117
|
+
This implementation follows best practices for resource management in long-running
|
|
118
|
+
applications, including thread-safe singleton pattern, bounded queues to prevent memory
|
|
119
|
+
exhaustion, proper resource cleanup on shutdown, and fault-tolerant error handling with
|
|
120
|
+
detailed logging.
|
|
121
|
+
|
|
122
|
+
Thread safety is ensured through the use of locks, thread-safe queues, and atomic operations,
|
|
123
|
+
making this implementation suitable for multi-threaded and asyncio applications.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
_instances: Dict[str, "AeriResourceManager"] = {}
|
|
127
|
+
_lock = threading.RLock()
|
|
128
|
+
|
|
129
|
+
def __new__(
|
|
130
|
+
cls,
|
|
131
|
+
*,
|
|
132
|
+
public_key: str,
|
|
133
|
+
secret_key: str,
|
|
134
|
+
base_url: str,
|
|
135
|
+
environment: Optional[str] = None,
|
|
136
|
+
release: Optional[str] = None,
|
|
137
|
+
timeout: Optional[int] = None,
|
|
138
|
+
flush_at: Optional[int] = None,
|
|
139
|
+
flush_interval: Optional[float] = None,
|
|
140
|
+
httpx_client: Optional[httpx.Client] = None,
|
|
141
|
+
media_upload_thread_count: Optional[int] = None,
|
|
142
|
+
sample_rate: Optional[float] = None,
|
|
143
|
+
mask: Optional[MaskFunction] = None,
|
|
144
|
+
tracing_enabled: Optional[bool] = None,
|
|
145
|
+
blocked_instrumentation_scopes: Optional[List[str]] = None,
|
|
146
|
+
should_export_span: Optional[Callable[[ReadableSpan], bool]] = None,
|
|
147
|
+
additional_headers: Optional[Dict[str, str]] = None,
|
|
148
|
+
tracer_provider: Optional[TracerProvider] = None,
|
|
149
|
+
) -> "AeriResourceManager":
|
|
150
|
+
if public_key in cls._instances:
|
|
151
|
+
return cls._instances[public_key]
|
|
152
|
+
|
|
153
|
+
with cls._lock:
|
|
154
|
+
if public_key not in cls._instances:
|
|
155
|
+
instance = super(AeriResourceManager, cls).__new__(cls)
|
|
156
|
+
|
|
157
|
+
# Initialize tracer (will be noop until init instance)
|
|
158
|
+
instance._otel_tracer = otel_trace_api.get_tracer(
|
|
159
|
+
AERI_TRACER_NAME,
|
|
160
|
+
aeri_version,
|
|
161
|
+
attributes={"public_key": public_key},
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
instance._initialize_instance(
|
|
165
|
+
public_key=public_key,
|
|
166
|
+
secret_key=secret_key,
|
|
167
|
+
base_url=base_url,
|
|
168
|
+
timeout=timeout,
|
|
169
|
+
environment=environment,
|
|
170
|
+
release=release,
|
|
171
|
+
flush_at=flush_at,
|
|
172
|
+
flush_interval=flush_interval,
|
|
173
|
+
httpx_client=httpx_client,
|
|
174
|
+
media_upload_thread_count=media_upload_thread_count,
|
|
175
|
+
sample_rate=sample_rate,
|
|
176
|
+
mask=mask,
|
|
177
|
+
tracing_enabled=tracing_enabled
|
|
178
|
+
if tracing_enabled is not None
|
|
179
|
+
else True,
|
|
180
|
+
blocked_instrumentation_scopes=blocked_instrumentation_scopes,
|
|
181
|
+
should_export_span=should_export_span,
|
|
182
|
+
additional_headers=additional_headers,
|
|
183
|
+
tracer_provider=tracer_provider,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
cls._instances[public_key] = instance
|
|
187
|
+
|
|
188
|
+
return cls._instances[public_key]
|
|
189
|
+
|
|
190
|
+
def _initialize_instance(
|
|
191
|
+
self,
|
|
192
|
+
*,
|
|
193
|
+
public_key: str,
|
|
194
|
+
secret_key: str,
|
|
195
|
+
base_url: str,
|
|
196
|
+
environment: Optional[str] = None,
|
|
197
|
+
release: Optional[str] = None,
|
|
198
|
+
timeout: Optional[int] = None,
|
|
199
|
+
flush_at: Optional[int] = None,
|
|
200
|
+
flush_interval: Optional[float] = None,
|
|
201
|
+
media_upload_thread_count: Optional[int] = None,
|
|
202
|
+
httpx_client: Optional[httpx.Client] = None,
|
|
203
|
+
sample_rate: Optional[float] = None,
|
|
204
|
+
mask: Optional[MaskFunction] = None,
|
|
205
|
+
tracing_enabled: bool = True,
|
|
206
|
+
blocked_instrumentation_scopes: Optional[List[str]] = None,
|
|
207
|
+
should_export_span: Optional[Callable[[ReadableSpan], bool]] = None,
|
|
208
|
+
additional_headers: Optional[Dict[str, str]] = None,
|
|
209
|
+
tracer_provider: Optional[TracerProvider] = None,
|
|
210
|
+
) -> None:
|
|
211
|
+
self.public_key = public_key
|
|
212
|
+
self.secret_key = secret_key
|
|
213
|
+
self.tracing_enabled = tracing_enabled
|
|
214
|
+
self.base_url = base_url
|
|
215
|
+
self.mask = mask
|
|
216
|
+
self.environment = environment
|
|
217
|
+
|
|
218
|
+
# Store additional client settings for get_client() to use
|
|
219
|
+
self.timeout = timeout
|
|
220
|
+
self.flush_at = flush_at
|
|
221
|
+
self.flush_interval = flush_interval
|
|
222
|
+
self.release = release
|
|
223
|
+
self.media_upload_thread_count = media_upload_thread_count
|
|
224
|
+
self.sample_rate = sample_rate
|
|
225
|
+
self.blocked_instrumentation_scopes = blocked_instrumentation_scopes
|
|
226
|
+
self.should_export_span = should_export_span
|
|
227
|
+
self.additional_headers = additional_headers
|
|
228
|
+
self.tracer_provider: Optional[TracerProvider] = None
|
|
229
|
+
|
|
230
|
+
# OTEL Tracer
|
|
231
|
+
if tracing_enabled:
|
|
232
|
+
tracer_provider = tracer_provider or _init_tracer_provider(
|
|
233
|
+
environment=environment, release=release, sample_rate=sample_rate
|
|
234
|
+
)
|
|
235
|
+
self.tracer_provider = tracer_provider
|
|
236
|
+
|
|
237
|
+
aeri_processor = AeriSpanProcessor(
|
|
238
|
+
public_key=self.public_key,
|
|
239
|
+
secret_key=secret_key,
|
|
240
|
+
base_url=base_url,
|
|
241
|
+
timeout=timeout,
|
|
242
|
+
flush_at=flush_at,
|
|
243
|
+
flush_interval=flush_interval,
|
|
244
|
+
blocked_instrumentation_scopes=blocked_instrumentation_scopes,
|
|
245
|
+
should_export_span=should_export_span,
|
|
246
|
+
additional_headers=additional_headers,
|
|
247
|
+
)
|
|
248
|
+
tracer_provider.add_span_processor(aeri_processor)
|
|
249
|
+
|
|
250
|
+
self._otel_tracer = tracer_provider.get_tracer(
|
|
251
|
+
AERI_TRACER_NAME,
|
|
252
|
+
aeri_version,
|
|
253
|
+
attributes={"public_key": self.public_key},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# API Clients
|
|
257
|
+
|
|
258
|
+
## API clients must be singletons because the underlying HTTPX clients
|
|
259
|
+
## use connection pools with limited capacity. Creating multiple instances
|
|
260
|
+
## could exhaust the OS's maximum number of available TCP sockets (file descriptors),
|
|
261
|
+
## leading to connection errors.
|
|
262
|
+
if httpx_client is not None:
|
|
263
|
+
self.httpx_client = httpx_client
|
|
264
|
+
else:
|
|
265
|
+
# Create a new httpx client with additional_headers if provided
|
|
266
|
+
client_headers = additional_headers if additional_headers else {}
|
|
267
|
+
self.httpx_client = httpx.Client(timeout=timeout, headers=client_headers)
|
|
268
|
+
|
|
269
|
+
self.api = AeriAPI(
|
|
270
|
+
base_url=base_url,
|
|
271
|
+
username=self.public_key,
|
|
272
|
+
password=secret_key,
|
|
273
|
+
x_aeri_sdk_name="python",
|
|
274
|
+
x_aeri_sdk_version=aeri_version,
|
|
275
|
+
x_aeri_public_key=self.public_key,
|
|
276
|
+
httpx_client=self.httpx_client,
|
|
277
|
+
timeout=timeout,
|
|
278
|
+
)
|
|
279
|
+
self.async_api = AsyncAeriAPI(
|
|
280
|
+
base_url=base_url,
|
|
281
|
+
username=self.public_key,
|
|
282
|
+
password=secret_key,
|
|
283
|
+
x_aeri_sdk_name="python",
|
|
284
|
+
x_aeri_sdk_version=aeri_version,
|
|
285
|
+
x_aeri_public_key=self.public_key,
|
|
286
|
+
timeout=timeout,
|
|
287
|
+
)
|
|
288
|
+
# Media
|
|
289
|
+
self._media_upload_enabled = os.environ.get(
|
|
290
|
+
AERI_MEDIA_UPLOAD_ENABLED, "True"
|
|
291
|
+
).lower() not in ("false", "0")
|
|
292
|
+
|
|
293
|
+
self._media_upload_queue: Queue[Any] = Queue(100_000)
|
|
294
|
+
self._media_manager = MediaManager(
|
|
295
|
+
api_client=self.api,
|
|
296
|
+
httpx_client=self.httpx_client,
|
|
297
|
+
media_upload_queue=self._media_upload_queue,
|
|
298
|
+
max_retries=3,
|
|
299
|
+
)
|
|
300
|
+
self._media_upload_consumers = []
|
|
301
|
+
|
|
302
|
+
media_upload_thread_count = media_upload_thread_count or max(
|
|
303
|
+
int(os.getenv(AERI_MEDIA_UPLOAD_THREAD_COUNT, 1)), 1
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
if self._media_upload_enabled:
|
|
307
|
+
for i in range(media_upload_thread_count):
|
|
308
|
+
media_upload_consumer = MediaUploadConsumer(
|
|
309
|
+
identifier=i,
|
|
310
|
+
media_manager=self._media_manager,
|
|
311
|
+
)
|
|
312
|
+
media_upload_consumer.start()
|
|
313
|
+
self._media_upload_consumers.append(media_upload_consumer)
|
|
314
|
+
|
|
315
|
+
# Prompt cache
|
|
316
|
+
self.prompt_cache = PromptCache()
|
|
317
|
+
|
|
318
|
+
# Score / trace ingestion — fully async task manager replaces thread-based consumer
|
|
319
|
+
self._task_manager = AsyncIngestionTaskManager(
|
|
320
|
+
async_client=self.async_api,
|
|
321
|
+
public_key=self.public_key,
|
|
322
|
+
flush_at=flush_at,
|
|
323
|
+
flush_interval=flush_interval,
|
|
324
|
+
max_retries=3,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Register shutdown handler
|
|
328
|
+
atexit.register(self.shutdown)
|
|
329
|
+
|
|
330
|
+
aeri_logger.info(
|
|
331
|
+
f"Startup: Aeri tracer successfully initialized | "
|
|
332
|
+
f"public_key={self.public_key} | "
|
|
333
|
+
f"base_url={base_url} | "
|
|
334
|
+
f"environment={environment or 'default'} | "
|
|
335
|
+
f"sample_rate={sample_rate if sample_rate is not None else 1.0} | "
|
|
336
|
+
f"media_threads={media_upload_thread_count or 1}"
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
@classmethod
|
|
340
|
+
def reset(cls) -> None:
|
|
341
|
+
with cls._lock:
|
|
342
|
+
for key in cls._instances:
|
|
343
|
+
cls._instances[key].shutdown()
|
|
344
|
+
|
|
345
|
+
cls._instances.clear()
|
|
346
|
+
|
|
347
|
+
def add_score_task(self, event: Dict[str, Any], *, force_sample: bool = False) -> None:
|
|
348
|
+
try:
|
|
349
|
+
validated_event = _StrictScoreQueueEvent.model_validate(event)
|
|
350
|
+
trace_id = validated_event.body.trace_id
|
|
351
|
+
|
|
352
|
+
# Sample scores with the same sampler that is used for tracing
|
|
353
|
+
tracer_provider = cast(TracerProvider, otel_trace_api.get_tracer_provider())
|
|
354
|
+
should_sample = (
|
|
355
|
+
force_sample
|
|
356
|
+
or isinstance(
|
|
357
|
+
tracer_provider, otel_trace_api.ProxyTracerProvider
|
|
358
|
+
) # default to in-sample if otel sampler is not available
|
|
359
|
+
or (
|
|
360
|
+
(
|
|
361
|
+
tracer_provider.sampler.should_sample(
|
|
362
|
+
parent_context=None,
|
|
363
|
+
trace_id=int(trace_id, 16),
|
|
364
|
+
name="score",
|
|
365
|
+
).decision
|
|
366
|
+
== Decision.RECORD_AND_SAMPLE
|
|
367
|
+
if trace_id is not None
|
|
368
|
+
else True
|
|
369
|
+
)
|
|
370
|
+
if trace_id
|
|
371
|
+
is not None # do not sample out session / dataset run scores
|
|
372
|
+
else True
|
|
373
|
+
)
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
if should_sample:
|
|
377
|
+
aeri_logger.debug(
|
|
378
|
+
f"Score: Enqueuing event type={validated_event.type} for trace_id={trace_id} name={validated_event.body.name} value={validated_event.body.value}"
|
|
379
|
+
)
|
|
380
|
+
self._task_manager.submit_event(event)
|
|
381
|
+
|
|
382
|
+
except ValidationError as e:
|
|
383
|
+
aeri_logger.error(
|
|
384
|
+
f"Validation error: Failed to enqueue score event due to invalid payload. Error details: {e}"
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
return
|
|
388
|
+
except Exception as e:
|
|
389
|
+
aeri_logger.error(
|
|
390
|
+
f"Unexpected error: Failed to process score event. The score will be dropped. Error details: {e}"
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
def add_trace_task(
|
|
396
|
+
self,
|
|
397
|
+
event: Dict[str, Any],
|
|
398
|
+
) -> None:
|
|
399
|
+
try:
|
|
400
|
+
validated_event = _StrictTraceQueueEvent.model_validate(event)
|
|
401
|
+
|
|
402
|
+
aeri_logger.debug(
|
|
403
|
+
f"Trace: Enqueuing event type={validated_event.type} for trace_id={validated_event.body.id}"
|
|
404
|
+
)
|
|
405
|
+
self._task_manager.submit_event(event)
|
|
406
|
+
|
|
407
|
+
except Full:
|
|
408
|
+
aeri_logger.warning(
|
|
409
|
+
"System overload: Trace ingestion queue has reached capacity (100,000 items). Trace update will be dropped. Consider increasing flush frequency or decreasing event volume."
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
return
|
|
413
|
+
except ValidationError as e:
|
|
414
|
+
aeri_logger.error(
|
|
415
|
+
f"Validation error: Failed to enqueue trace event due to invalid payload. Error details: {e}"
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
return
|
|
419
|
+
except Exception as e:
|
|
420
|
+
aeri_logger.error(
|
|
421
|
+
f"Unexpected error: Failed to process trace event. The trace update will be dropped. Error details: {e}"
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
return
|
|
425
|
+
|
|
426
|
+
@property
|
|
427
|
+
def tracer(self) -> Optional[Tracer]:
|
|
428
|
+
return self._otel_tracer
|
|
429
|
+
|
|
430
|
+
@staticmethod
|
|
431
|
+
def get_current_span() -> Any:
|
|
432
|
+
return otel_trace_api.get_current_span()
|
|
433
|
+
|
|
434
|
+
def _stop_and_join_consumer_threads(self) -> None:
|
|
435
|
+
"""Drain and stop all background workers."""
|
|
436
|
+
aeri_logger.debug(
|
|
437
|
+
f"Shutdown: Waiting for {len(self._media_upload_consumers)} media upload thread(s) to complete processing"
|
|
438
|
+
)
|
|
439
|
+
for media_upload_consumer in self._media_upload_consumers:
|
|
440
|
+
media_upload_consumer.pause()
|
|
441
|
+
|
|
442
|
+
for media_upload_consumer in self._media_upload_consumers:
|
|
443
|
+
try:
|
|
444
|
+
media_upload_consumer.join()
|
|
445
|
+
except RuntimeError:
|
|
446
|
+
pass
|
|
447
|
+
|
|
448
|
+
aeri_logger.debug(
|
|
449
|
+
f"Shutdown: Media upload thread #{media_upload_consumer._identifier} successfully terminated"
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
aeri_logger.debug("Shutdown: Stopping async ingestion task manager …")
|
|
453
|
+
self._task_manager.shutdown()
|
|
454
|
+
aeri_logger.debug("Shutdown: Async ingestion task manager stopped.")
|
|
455
|
+
|
|
456
|
+
def flush(self) -> None:
|
|
457
|
+
if self.tracer_provider is not None and not isinstance(
|
|
458
|
+
self.tracer_provider, otel_trace_api.ProxyTracerProvider
|
|
459
|
+
):
|
|
460
|
+
self.tracer_provider.force_flush()
|
|
461
|
+
aeri_logger.debug("Successfully flushed OTEL tracer provider")
|
|
462
|
+
|
|
463
|
+
self._task_manager.flush()
|
|
464
|
+
aeri_logger.debug("Successfully flushed async ingestion task manager")
|
|
465
|
+
|
|
466
|
+
self._media_upload_queue.join()
|
|
467
|
+
aeri_logger.debug("Successfully flushed media upload queue")
|
|
468
|
+
|
|
469
|
+
def shutdown(self) -> None:
|
|
470
|
+
# Unregister the atexit handler first
|
|
471
|
+
atexit.unregister(self.shutdown)
|
|
472
|
+
|
|
473
|
+
self.flush()
|
|
474
|
+
self._stop_and_join_consumer_threads()
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _init_tracer_provider(
|
|
478
|
+
*,
|
|
479
|
+
environment: Optional[str] = None,
|
|
480
|
+
release: Optional[str] = None,
|
|
481
|
+
sample_rate: Optional[float] = None,
|
|
482
|
+
) -> TracerProvider:
|
|
483
|
+
environment = environment or os.environ.get(AERI_TRACING_ENVIRONMENT)
|
|
484
|
+
release = release or os.environ.get(AERI_RELEASE) or get_common_release_envs()
|
|
485
|
+
|
|
486
|
+
resource_attributes = {
|
|
487
|
+
AeriOtelSpanAttributes.ENVIRONMENT: environment,
|
|
488
|
+
AeriOtelSpanAttributes.RELEASE: release,
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
resource = Resource.create(
|
|
492
|
+
{k: v for k, v in resource_attributes.items() if v is not None}
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
provider = None
|
|
496
|
+
default_provider = cast(TracerProvider, otel_trace_api.get_tracer_provider())
|
|
497
|
+
|
|
498
|
+
if isinstance(default_provider, otel_trace_api.ProxyTracerProvider):
|
|
499
|
+
provider = TracerProvider(
|
|
500
|
+
resource=resource,
|
|
501
|
+
sampler=TraceIdRatioBased(sample_rate)
|
|
502
|
+
if sample_rate is not None and sample_rate < 1
|
|
503
|
+
else None,
|
|
504
|
+
)
|
|
505
|
+
otel_trace_api.set_tracer_provider(provider)
|
|
506
|
+
|
|
507
|
+
else:
|
|
508
|
+
provider = default_provider
|
|
509
|
+
|
|
510
|
+
return provider
|