def test_maintanence_routine(self):
     with mock.patch("os.makedirs") as m:
         m.return_value = None
         self.assertRaises(
             Exception,
             lambda: LocalFileStorage(os.path.join(TEST_FOLDER, "baz")),
         )
     with mock.patch("os.makedirs", side_effect=throw(Exception)):
         self.assertRaises(
             Exception,
             lambda: LocalFileStorage(os.path.join(TEST_FOLDER, "baz")),
         )
     with mock.patch("os.listdir", side_effect=throw(Exception)):
         self.assertRaises(
             Exception,
             lambda: LocalFileStorage(os.path.join(TEST_FOLDER, "baz")),
         )
     with LocalFileStorage(os.path.join(TEST_FOLDER, "baz")) as stor:
         with mock.patch("os.listdir", side_effect=throw(Exception)):
             stor._maintenance_routine(silent=True)
             self.assertRaises(
                 Exception, lambda: stor._maintenance_routine()
             )
         with mock.patch("os.path.isdir", side_effect=throw(Exception)):
             stor._maintenance_routine(silent=True)
             self.assertRaises(
                 Exception, lambda: stor._maintenance_routine()
             )
 def __init__(self, **options):
     self._telemetry_processors = []
     self.options = ExporterOptions(**options)
     self.storage = LocalFileStorage(
         path=self.options.storage_path,
         max_size=self.options.storage_max_size,
         maintenance_period=self.options.storage_maintenance_period,
         retention_period=self.options.storage_retention_period,
     )
Esempio n. 3
0
 def test_put(self):
     test_input = (1, 2, 3)
     with LocalFileStorage(os.path.join(TEST_FOLDER, "bar")) as stor:
         stor.put(test_input)
         self.assertEqual(stor.get().get(), test_input)
     with LocalFileStorage(os.path.join(TEST_FOLDER, "bar")) as stor:
         self.assertEqual(stor.get().get(), test_input)
         with mock.patch("os.rename", side_effect=throw(Exception)):
             self.assertIsNone(stor.put(test_input))
 def test_check_storage_size_links(self):
     test_input = (1, 2, 3)
     with LocalFileStorage(os.path.join(TEST_FOLDER, "asd4"), 1000) as stor:
         stor.put(test_input)
         with mock.patch("os.path.islink") as os_mock:
             os_mock.return_value = True
         self.assertTrue(stor._check_storage_size())
 def test_check_storage_size_error(self):
     test_input = (1, 2, 3)
     with LocalFileStorage(os.path.join(TEST_FOLDER, "asd5"), 1) as stor:
         with mock.patch("os.path.getsize", side_effect=throw(OSError)):
             stor.put(test_input)
             with mock.patch("os.path.islink") as os_mock:
                 os_mock.return_value = True
             self.assertTrue(stor._check_storage_size())
 def test_put_max_size(self):
     test_input = (1, 2, 3)
     with LocalFileStorage(os.path.join(TEST_FOLDER, "asd")) as stor:
         size_mock = mock.Mock()
         size_mock.return_value = False
         stor._check_storage_size = size_mock
         stor.put(test_input)
         self.assertEqual(stor.get(), None)
 def test_get(self):
     now = _now()
     with LocalFileStorage(os.path.join(TEST_FOLDER, "foo")) as stor:
         stor.put((1, 2, 3), lease_period=10)
         with mock.patch("azure_monitor.storage._now") as m:
             m.return_value = now - _seconds(30 * 24 * 60 * 60)
             stor.put((1, 2, 3))
             stor.put((1, 2, 3), lease_period=10)
             with mock.patch("os.rename"):
                 stor.put((1, 2, 3))
         with mock.patch("os.rename"):
             stor.put((1, 2, 3))
         with mock.patch("os.remove", side_effect=throw(Exception)):
             with mock.patch("os.rename", side_effect=throw(Exception)):
                 self.assertIsNone(stor.get())
         self.assertIsNone(stor.get())
 def test_get_nothing(self):
     with LocalFileStorage(os.path.join(TEST_FOLDER, "test", "a")) as stor:
         pass
     with LocalFileStorage(os.path.join(TEST_FOLDER, "test")) as stor:
         self.assertIsNone(stor.get())
 def test_check_storage_size_no_files(self):
     with LocalFileStorage(os.path.join(TEST_FOLDER, "asd3"), 1000) as stor:
         self.assertTrue(stor._check_storage_size())
 def test_check_storage_size_not_full(self):
     test_input = (1, 2, 3)
     with LocalFileStorage(os.path.join(TEST_FOLDER, "asd3"), 1000) as stor:
         stor.put(test_input)
         self.assertTrue(stor._check_storage_size())
class BaseExporter:
    """Azure Monitor base exporter for OpenTelemetry.

    Args:
        options: :doc:`export.options` to allow configuration for the exporter
    """

    def __init__(self, **options):
        self._telemetry_processors = []
        self.options = ExporterOptions(**options)
        self.storage = LocalFileStorage(
            path=self.options.storage_path,
            max_size=self.options.storage_max_size,
            maintenance_period=self.options.storage_maintenance_period,
            retention_period=self.options.storage_retention_period,
        )

    def add_telemetry_processor(
        self, processor: typing.Callable[..., any]
    ) -> None:
        """Adds telemetry processor to the collection.

        Telemetry processors will be called one by one before telemetry
        item is pushed for sending and in the order they were added.

        Args:
            processor: Processor to add
        """
        self._telemetry_processors.append(processor)

    def clear_telemetry_processors(self) -> None:
        """Removes all telemetry processors"""
        self._telemetry_processors = []

    def _apply_telemetry_processors(
        self, envelopes: typing.List[Envelope]
    ) -> typing.List[Envelope]:
        """Applies all telemetry processors in the order they were added.

        This function will return the list of envelopes to be exported after
        each processor has been run sequentially. Individual processors can
        throw exceptions and fail, but the applying of all telemetry processors
        will proceed (not fast fail). Processors also return True if envelope
        should be included for exporting, False otherwise.

        Args:
            envelopes: The envelopes to apply each processor to.
        """
        filtered_envelopes = []
        for envelope in envelopes:
            accepted = True
            for processor in self._telemetry_processors:
                try:
                    if processor(envelope) is False:
                        accepted = False
                        break
                except Exception as ex:
                    logger.warning("Telemetry processor failed with: %s.", ex)
            if accepted:
                filtered_envelopes.append(envelope)
        return filtered_envelopes

    def _transmit_from_storage(self) -> None:
        for blob in self.storage.gets():
            # give a few more seconds for blob lease operation
            # to reduce the chance of race (for perf consideration)
            if blob.lease(self.options.timeout + 5):
                envelopes = blob.get()  # TODO: handle error
                result = self._transmit(envelopes)
                if result == ExportResult.FAILED_RETRYABLE:
                    blob.lease(1)
                else:
                    blob.delete(silent=True)

    # pylint: disable=too-many-branches
    # pylint: disable=too-many-nested-blocks
    def _transmit(self, envelopes: typing.List[Envelope]) -> ExportResult:
        """
        Transmit the data envelopes to the ingestion service.

        Returns an ExportResult, this function should never
        throw an exception.
        """
        if len(envelopes) > 0:
            try:
                response = requests.post(
                    url=self.options.endpoint,
                    data=json.dumps(envelopes),
                    headers={
                        "Accept": "application/json",
                        "Content-Type": "application/json; charset=utf-8",
                    },
                    timeout=self.options.timeout,
                )
            except Exception as ex:
                logger.warning("Transient client side error: %s.", ex)
                return ExportResult.FAILED_RETRYABLE

            text = "N/A"
            data = None
            try:
                text = response.text
            except Exception as ex:
                logger.warning("Error while reading response body %s.", ex)
            else:
                try:
                    data = json.loads(text)
                except Exception:
                    pass

            if response.status_code == 200:
                logger.info("Transmission succeeded: %s.", text)
                return ExportResult.SUCCESS
            if response.status_code == 206:  # Partial Content
                # TODO: store the unsent data
                if data:
                    try:
                        resend_envelopes = []
                        for error in data["errors"]:
                            if error["statusCode"] in (
                                429,  # Too Many Requests
                                439,  # Too Many Requests over extended time
                                500,  # Internal Server Error
                                503,  # Service Unavailable
                            ):
                                resend_envelopes.append(
                                    envelopes[error["index"]]
                                )
                            else:
                                logger.error(
                                    "Data drop %s: %s %s.",
                                    error["statusCode"],
                                    error["message"],
                                    envelopes[error["index"]],
                                )
                        if resend_envelopes:
                            self.storage.put(resend_envelopes)
                    except Exception as ex:
                        logger.error(
                            "Error while processing %s: %s %s.",
                            response.status_code,
                            text,
                            ex,
                        )
                    return ExportResult.FAILED_NOT_RETRYABLE
                # cannot parse response body, fallback to retry

            if response.status_code in (
                206,  # Partial Content
                429,  # Too Many Requests
                439,  # Too Many Requests over extended time
                500,  # Internal Server Error
                503,  # Service Unavailable
            ):
                return ExportResult.FAILED_RETRYABLE

            return ExportResult.FAILED_NOT_RETRYABLE
        # No spans to export
        return ExportResult.SUCCESS
Esempio n. 12
0
 def test_maintanence_routine(self):
     with mock.patch("os.makedirs") as m:
         with LocalFileStorage(os.path.join(TEST_FOLDER, "baz")) as stor:
             m.return_value = None
     with mock.patch("os.makedirs", side_effect=throw(Exception)):
         stor = LocalFileStorage(os.path.join(TEST_FOLDER, "baz"))
         stor.close()
     with mock.patch("os.listdir", side_effect=throw(Exception)):
         stor = LocalFileStorage(os.path.join(TEST_FOLDER, "baz"))
         stor.close()
     with LocalFileStorage(os.path.join(TEST_FOLDER, "baz")) as stor:
         with mock.patch("os.listdir", side_effect=throw(Exception)):
             stor._maintenance_routine()
         with mock.patch("os.path.isdir", side_effect=throw(Exception)):
             stor._maintenance_routine()