Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Added Diagnostic Logging for App Service
([#212](https://github.com/microsoft/ApplicationInsights-Python/pull/212))
- Updated main and distro READMEs
([#205](https://github.com/microsoft/ApplicationInsights-Python/pull/205))
- Update CONTRIBUTING.md, support Py3.11
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import logging
import platform
from os import environ
from pathlib import Path

from azure.monitor.opentelemetry.exporter._connection_string_parser import (
ConnectionStringParser,
)

# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License in the project root for
# license information.
# --------------------------------------------------------------------------

_LOG_PATH_LINUX = "/var/log/applicationinsights"
_LOG_PATH_WINDOWS = "\\LogFiles\\ApplicationInsights"
_IS_ON_APP_SERVICE = "WEBSITE_SITE_NAME" in environ
# TODO: Add environment variable to enabled diagnostics off of App Service
_IS_DIAGNOSTICS_ENABLED = _IS_ON_APP_SERVICE
# TODO: Enabled when duplciate logging issue is solved
# _EXPORTER_DIAGNOSTICS_ENABLED_ENV_VAR = (
# "AZURE_MONITOR_OPENTELEMETRY_DISTRO_ENABLE_EXPORTER_DIAGNOSTICS"
# )
logger = logging.getLogger(__name__)
_CUSTOMER_IKEY = "unknown"
try:
_CUSTOMER_IKEY = ConnectionStringParser().instrumentation_key
except ValueError as e:
logger.error("Failed to parse Instrumentation Key: %s" % e)


def _get_log_path(status_log_path=False):
system = platform.system()
if system == "Linux":
return _LOG_PATH_LINUX
elif system == "Windows":
log_path = str(Path.home()) + _LOG_PATH_WINDOWS
if status_log_path:
return log_path + "\\status"
else:
return log_path
else:
return None


def _env_var_or_default(var_name, default_val=""):
try:
return environ[var_name]
except KeyError:
return default_val


# TODO: Enabled when duplciate logging issue is solved
# def _is_exporter_diagnostics_enabled():
# return (
# _EXPORTER_DIAGNOSTICS_ENABLED_ENV_VAR in environ
# and environ[_EXPORTER_DIAGNOSTICS_ENABLED_ENV_VAR] == "True"
# )


_EXTENSION_VERSION = _env_var_or_default(
"ApplicationInsightsAgent_EXTENSION_VERSION", "disabled"
)
# TODO: Enabled when duplciate logging issue is solved
# _EXPORTER_DIAGNOSTICS_ENABLED = _is_exporter_diagnostics_enabled()
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License in the project root for
# license information.
# --------------------------------------------------------------------------

import logging
import threading
from os import makedirs
from os.path import exists, join

from azure.monitor.opentelemetry.distro._constants import (
_CUSTOMER_IKEY,
_EXTENSION_VERSION,
_IS_DIAGNOSTICS_ENABLED,
_env_var_or_default,
_get_log_path,
)
from azure.monitor.opentelemetry.distro._version import VERSION

_OPENTELEMETRY_DIAGNOSTIC_LOGGER_NAME = "opentelemetry"
_DIAGNOSTIC_LOGGER_FILE_NAME = "applicationinsights-extension.log"
_SITE_NAME = _env_var_or_default("WEBSITE_SITE_NAME")
_SUBSCRIPTION_ID_ENV_VAR = _env_var_or_default("WEBSITE_OWNER_NAME")
_SUBSCRIPTION_ID = (
_SUBSCRIPTION_ID_ENV_VAR.split("+")[0]
if _SUBSCRIPTION_ID_ENV_VAR
else None
)
_opentelemetry_logger = logging.getLogger(
_OPENTELEMETRY_DIAGNOSTIC_LOGGER_NAME
)
_logger = logging.getLogger(__name__)
_DIAGNOSTIC_LOG_PATH = _get_log_path()


class AzureDiagnosticLogging:
_initialized = False
_lock = threading.Lock()
_f_handler = None

def _initialize():
with AzureDiagnosticLogging._lock:
if not AzureDiagnosticLogging._initialized:
if _IS_DIAGNOSTICS_ENABLED and _DIAGNOSTIC_LOG_PATH:
format = (
"{"
+ '"time":"%(asctime)s.%(msecs)03d", '
+ '"level":"%(levelname)s", '
+ '"logger":"%(name)s", '
+ '"message":"%(message)s", '
+ '"properties":{'
+ '"operation":"Startup", '
+ f'"sitename":"{_SITE_NAME}", '
+ f'"ikey":"{_CUSTOMER_IKEY}", '
+ f'"extensionVersion":"{_EXTENSION_VERSION}", '
+ f'"sdkVersion":"{VERSION}", '
+ f'"subscriptionId":"{_SUBSCRIPTION_ID}", '
+ '"language":"python"'
+ "}"
+ "}"
)
if not exists(_DIAGNOSTIC_LOG_PATH):
makedirs(_DIAGNOSTIC_LOG_PATH)
AzureDiagnosticLogging._f_handler = logging.FileHandler(
join(
_DIAGNOSTIC_LOG_PATH, _DIAGNOSTIC_LOGGER_FILE_NAME
)
)
formatter = logging.Formatter(
fmt=format, datefmt="%Y-%m-%dT%H:%M:%S"
)
AzureDiagnosticLogging._f_handler.setFormatter(formatter)
_logger.addHandler(AzureDiagnosticLogging._f_handler)
AzureDiagnosticLogging._initialized = True
_logger.info("Initialized Azure Diagnostic Logger.")

def enable(logger: logging.Logger):
AzureDiagnosticLogging._initialize()
if AzureDiagnosticLogging._initialized:
logger.addHandler(AzureDiagnosticLogging._f_handler)
_logger.info(
"Added Azure diagnostics logging to %s." % logger.name
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,24 @@
# license information.
# --------------------------------------------------------------------------


from azure.monitor.opentelemetry.distro._diagnostic_logging import (
AzureDiagnosticLogging,
)
from opentelemetry.sdk._configuration import _OTelSDKConfigurator


class AzureMonitorConfigurator(_OTelSDKConfigurator):
pass
def _configure(self, **kwargs):
try:
super()._configure(**kwargs)
except ValueError as e:
AzureDiagnosticLogging.log_diagnostic_error(
f"The components failed to initialize due to a ValueError: {e}"
)
raise e
except Exception as e:
AzureDiagnosticLogging.log_diagnostic_error(
f"The components failed to initialize: {e}"
)
raise e
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
import logging
from os import environ

from azure.monitor.opentelemetry.distro._diagnostic_logging import (
AzureDiagnosticLogging,
)
from opentelemetry.environment_variables import (
OTEL_METRICS_EXPORTER,
OTEL_TRACES_EXPORTER,
)
from opentelemetry.instrumentation.distro import BaseDistro

_logger = logging.getLogger(__name__)
_opentelemetry_logger = logging.getLogger("opentelemetry")
# TODO: Enabled when duplicate logging issue is solved
# _exporter_logger = logging.getLogger("azure.monitor.opentelemetry.exporter")


class AzureMonitorDistro(BaseDistro):
Expand All @@ -27,12 +33,30 @@ def _configure(self, **kwargs) -> None:


def _configure_auto_instrumentation() -> None:
# TODO: support configuration via env vars
# TODO: Uncomment when logging is out of preview
# environ.setdefault(OTEL_LOGS_EXPORTER, "azure_monitor_opentelemetry_exporter")
environ.setdefault(
OTEL_METRICS_EXPORTER, "azure_monitor_opentelemetry_exporter"
)
environ.setdefault(
OTEL_TRACES_EXPORTER, "azure_monitor_opentelemetry_exporter"
)
try:
AzureDiagnosticLogging.enable(_logger)
AzureDiagnosticLogging.enable(_opentelemetry_logger)
# TODO: Enabled when duplicate logging issue is solved
# if _EXPORTER_DIAGNOSTICS_ENABLED:
# exporter_logger = logging.getLogger(
# "azure.monitor.opentelemetry.exporter"
# )
# AzureDiagnosticLogging.enable(_exporter_logger)
# TODO: Uncomment when logging is out of preview
# environ.setdefault(OTEL_LOGS_EXPORTER,
# "azure_monitor_opentelemetry_exporter")
environ.setdefault(
OTEL_METRICS_EXPORTER, "azure_monitor_opentelemetry_exporter"
)
environ.setdefault(
OTEL_TRACES_EXPORTER, "azure_monitor_opentelemetry_exporter"
)
_logger.info(
"Azure Monitor OpenTelemetry Distro configured successfully."
)
except Exception as exc:
_logger.error(
"Azure Monitor OpenTelemetry Distro failed during "
+ f"configuration: {exc}"
)
raise exc
149 changes: 149 additions & 0 deletions azure-monitor-opentelemetry-distro/tests/test_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License in the project root for
# license information.
# --------------------------------------------------------------------------

from importlib import reload
from os import environ
from unittest import TestCase
from unittest.mock import patch

from azure.monitor.opentelemetry.distro import _constants

TEST_VALUE = "TEST_VALUE"
TEST_IKEY = "1234abcd-ab12-34cd-ab12-a23456abcdef"
TEST_CONN_STR = f"InstrumentationKey={TEST_IKEY};IngestionEndpoint=https://centralus-2.in.applicationinsights.azure.com/;LiveEndpoint=https://centralus.livediagnostics.monitor.azure.com/"


def clear_env_var(env_var):
if env_var in environ:
del environ[env_var]


class TestConstants(TestCase):
@patch.dict(
"os.environ",
{"ApplicationInsightsAgent_EXTENSION_VERSION": TEST_VALUE},
)
def test_extension_version(self):
reload(_constants)
self.assertEqual(_constants._EXTENSION_VERSION, TEST_VALUE)

def test_extension_version_default(self):
clear_env_var("ApplicationInsightsAgent_EXTENSION_VERSION")
reload(_constants)
self.assertEqual(_constants._EXTENSION_VERSION, "disabled")

@patch.dict(
"os.environ", {"APPLICATIONINSIGHTS_CONNECTION_STRING": TEST_CONN_STR}
)
def test_ikey(self):
reload(_constants)
self.assertEqual(_constants._CUSTOMER_IKEY, TEST_IKEY)

def test_ikey_defaults(self):
clear_env_var("APPLICATIONINSIGHTS_CONNECTION_STRING")
reload(_constants)
self.assertEqual(_constants._CUSTOMER_IKEY, "unknown")

# TODO: Enabled when duplciate logging issue is solved
# @patch.dict(
# "os.environ",
# {"AZURE_MONITOR_OPENTELEMETRY_DISTRO_ENABLE_EXPORTER_DIAGNOSTICS": "True"},
# )
# def test_exporter_diagnostics_enabled(self):
# reload(_constants)
# self.assertTrue(_constants._EXPORTER_DIAGNOSTICS_ENABLED)

# def test_exporter_diagnostics_disabled(self):
# clear_env_var("AZURE_MONITOR_OPENTELEMETRY_DISTRO_ENABLE_EXPORTER_DIAGNOSTICS")
# reload(_constants)
# self.assertFalse(_constants._EXPORTER_DIAGNOSTICS_ENABLED)

# @patch.dict(
# "os.environ",
# {"AZURE_MONITOR_OPENTELEMETRY_DISTRO_ENABLE_EXPORTER_DIAGNOSTICS": "foobar"},
# )
# def test_exporter_diagnostics_other(self):
# reload(_constants)
# self.assertFalse(_constants._EXPORTER_DIAGNOSTICS_ENABLED)

@patch.dict("os.environ", {"WEBSITE_SITE_NAME": TEST_VALUE})
def test_diagnostics_enabled(self):
reload(_constants)
self.assertTrue(_constants._IS_DIAGNOSTICS_ENABLED)

def test_diagnostics_disabled(self):
clear_env_var("WEBSITE_SITE_NAME")
reload(_constants)
self.assertFalse(_constants._IS_DIAGNOSTICS_ENABLED)

@patch(
"azure.monitor.opentelemetry.distro._constants.platform.system",
return_value="Linux",
)
def test_log_path_linux(self, mock_system):
self.assertEqual(
_constants._get_log_path(), "/var/log/applicationinsights"
)

@patch(
"azure.monitor.opentelemetry.distro._constants.platform.system",
return_value="Linux",
)
def test_status_log_path_linux(self, mock_system):
self.assertEqual(
_constants._get_log_path(status_log_path=True),
"/var/log/applicationinsights",
)

@patch(
"azure.monitor.opentelemetry.distro._constants.platform.system",
return_value="Windows",
)
@patch("pathlib.Path.home", return_value="\\HOME\\DIR")
def test_log_path_windows(self, mock_system, mock_home):
self.assertEqual(
_constants._get_log_path(),
"\\HOME\\DIR\\LogFiles\\ApplicationInsights",
)

@patch(
"azure.monitor.opentelemetry.distro._constants.platform.system",
return_value="Windows",
)
@patch("pathlib.Path.home", return_value="\\HOME\\DIR")
def test_status_log_path_windows(self, mock_system, mock_home):
self.assertEqual(
_constants._get_log_path(status_log_path=True),
"\\HOME\\DIR\\LogFiles\\ApplicationInsights\\status",
)

@patch(
"azure.monitor.opentelemetry.distro._constants.platform.system",
return_value="Window",
)
def test_log_path_other(self, mock_platform):
self.assertIsNone(_constants._get_log_path())

@patch(
"azure.monitor.opentelemetry.distro._constants.platform.system",
return_value="linux",
)
def test_status_log_path_other(self, mock_platform):
self.assertIsNone(_constants._get_log_path(status_log_path=True))

@patch.dict("os.environ", {"key": "value"})
def test_env_var_or_default(self):
self.assertEqual(_constants._env_var_or_default("key"), "value")

@patch.dict("os.environ", {})
def test_env_var_or_default_empty(self):
self.assertEqual(_constants._env_var_or_default("key"), "")

@patch.dict("os.environ", {})
def test_env_var_or_default_empty_with_defaults(self):
self.assertEqual(
_constants._env_var_or_default("key", default_val="value"), "value"
)
Loading