├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── SECURITY.md
├── azure_monitor
├── README.md
├── artifacts
│ ├── common.ps1
│ ├── parameters.json
│ ├── setup.ps1
│ └── template.json
├── azfunc_sample
│ ├── BaltimoreCyberTrustRoot.crt.pem
│ ├── DigiCertGlobalRootCA.crt.pem
│ ├── MyHttpFunction
│ │ ├── __init__.py
│ │ ├── function.json
│ │ └── sample.dat
│ ├── README.md
│ ├── docs
│ │ └── assets
│ │ │ ├── 01-ai-correlated-invocation.PNG
│ │ │ ├── 02-ai-correlated-invocation.PNG
│ │ │ └── 03-ai-application-map.PNG
│ ├── host.json
│ ├── instrumentation
│ │ ├── __init__.py
│ │ ├── function.json
│ │ ├── globals.py
│ │ ├── instrumentation_func.py
│ │ ├── main.py
│ │ └── utils.py
│ ├── local.settings.json
│ ├── requirements.txt
│ ├── tests
│ │ ├── __init__.py
│ │ ├── test_globals.py
│ │ └── test_instrumentation_func.py
│ └── tmp
│ │ └── data
│ │ ├── 05582fa0-2f83-48ca-96ae-8206ee59aab5.txt
│ │ ├── a20f2b98-bd5a-4692-ab93-397a460d722f.txt
│ │ └── bd7ae612-ca6a-4bb4-a146-9926f24a4e10.txt
├── django_sample
│ ├── .env.example
│ ├── .gitignore
│ ├── README.md
│ ├── azureproject
│ │ ├── __init__.py
│ │ ├── app_insights.py
│ │ ├── asgi.py
│ │ ├── development.py
│ │ ├── get_token.py
│ │ ├── production.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── manage.py
│ ├── requirements.txt
│ ├── restaurant_review
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── migrations
│ │ │ ├── 0001_initial.py
│ │ │ ├── 0002_alter_review_rating.py
│ │ │ ├── 0003_restaurant_image_name_review_image_name.py
│ │ │ ├── 0004_alter_restaurant_image_name_alter_review_image_name.py
│ │ │ ├── 0005_remove_restaurant_image_name.py
│ │ │ ├── 0006_alter_review_image_name.py
│ │ │ ├── 0007_alter_review_image_name.py
│ │ │ └── __init__.py
│ │ ├── models.py
│ │ ├── templates
│ │ │ ├── 500.html
│ │ │ └── restaurant_review
│ │ │ │ ├── base.html
│ │ │ │ ├── create_restaurant.html
│ │ │ │ ├── details.html
│ │ │ │ ├── index.html
│ │ │ │ └── star_rating.html
│ │ ├── templatetags
│ │ │ ├── __init__.py
│ │ │ └── restaurant_extras.py
│ │ ├── tests.py
│ │ ├── urls.py
│ │ └── views.py
│ ├── static
│ │ ├── favicon.ico
│ │ ├── images
│ │ │ ├── Example-reviews.png
│ │ │ └── azure-icon.svg
│ │ └── restaurant.css
│ ├── staticfiles
│ │ └── note.txt
│ └── web_project
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
├── env.template
├── flask_sample
│ ├── README.md
│ ├── app.db
│ ├── app
│ │ ├── __init__.py
│ │ ├── forms
│ │ │ └── __init__.py
│ │ ├── metrics.py
│ │ ├── models
│ │ │ └── __init__.py
│ │ ├── routes.py
│ │ ├── static
│ │ │ └── index.css
│ │ └── templates
│ │ │ ├── blacklist.html
│ │ │ └── index.html
│ ├── command.py
│ ├── config.py
│ ├── endpoint.py
│ ├── endpoint
│ │ ├── __init__.py
│ │ └── endpoint_routes.py
│ ├── output
│ │ └── file.txt
│ ├── requirements.txt
│ └── sample.py
├── media
│ ├── python_azure_dependencies.png
│ ├── python_azure_resources.png
│ ├── python_custommetrics-carrots-logs.png
│ ├── python_custommetrics-carrots.png
│ ├── python_custommetrics-web-reviews.png
│ ├── python_functionapp_deploy.png
│ ├── python_simple_app_trace.png
│ ├── python_simple_exception_custom.png
│ ├── python_simple_trace_trace.png
│ └── python_webapp_requests.png
├── python_logger_opencensus_azure
│ ├── README.md
│ └── monitoring
│ │ ├── examples
│ │ ├── README.md
│ │ ├── __init__.py
│ │ ├── api_1.py
│ │ ├── api_2.py
│ │ ├── api_3.py
│ │ ├── api_4.py
│ │ ├── client.py
│ │ ├── logging_config.json
│ │ └── util.py
│ │ ├── img
│ │ ├── application_map.png
│ │ ├── dependency.png
│ │ └── operation_id.png
│ │ ├── requirements.txt
│ │ ├── src
│ │ ├── __init__.py
│ │ └── logger.py
│ │ └── tests
│ │ ├── __init__.py
│ │ └── test_logger.py
└── simple_sample
│ ├── customDimensions.py
│ ├── database.py
│ ├── event.py
│ ├── metric.py
│ ├── metric2.py
│ ├── module1.py
│ ├── module2.py
│ ├── prompt.py
│ ├── readme.md
│ ├── spanComplex.py
│ ├── spanSimple.py
│ └── trace.py
├── setup.cfg
├── setup.py
└── version.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | # VSCode
132 | .vscode/
133 |
134 | # Azurite
135 | __azurite_*
136 | __blobstorage__/
137 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## Unreleased
4 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | page_type: sample
3 | languages:
4 | - python
5 | - html
6 | products:
7 | - azure
8 | description: "This repo contains sample applications to show how you can instrument the OpenCensus Azure Monitor exporters as well as track telemetry from popular Python libraries via OpenCensus integrations."
9 | urlFragment: azure-monitor-opencensus-python
10 | ---
11 |
12 | # Azure Monitor Python Sample Applications
13 |
14 | This repo contains following sample applications.
15 |
16 | ## Flask "To-Do" Sample Application
17 |
18 | Here is the [README](./azure_monitor/flask_sample/README.md) for the Flask "To-Do" Sample application.
19 |
20 | ## Python Logger class using Opencensus
21 |
22 | Here is the [README](./azure_monitor/python_logger_opencensus_azure/README.md) for the Python logger class Sample application.
23 |
24 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/azure_monitor/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Getting Started with App Insights for Python via OpenCensus Samples'
3 | description: Getting Started with App Insights for Python via OpenCensus Samples.
4 | author: givenscj
5 | ms.author: givenscj
6 | ms.devlang: python
7 | ms.topic: sample
8 | ms.date: 07/27/2022
9 | ms.custom: []
10 | ---
11 |
12 | # Getting Started with App Insights for Python via OpenCensus Samples
13 |
14 | Many of the samples in this folder support the https://docs.microsoft.com/azure/azure-monitor/app/opencensus-python articles. Reference these articles for more information on how OpenCensus works with Python and Application Insights.
15 |
16 | The following samples are available in this repo:
17 |
18 | - [Azure Functions - Utilizes dependency tracking via MySQL and Azure Storage](./azfunc_sample/README.md)
19 | - [Django - Utilizes custom metrics based on web application events in Azure App Service](./django_sample/README.md)
20 | - [Flask - Utilizes OpenCensus Request and SqlAlchemy dependency tracking](./flask_sample/README.md)
21 | - [Python (Simple) - Simple python applications that demonstrate OpenCensus distributed tracing, metrics and logging](./simple_sample/README.md)
22 | - [Python (Logger) - Python applications that utilize OpenCensus tracing and dependencies](./python_logger_opencensus_azure/README.md)
23 |
24 | You can also find more samples in the following repos and documentation links however you may find that you have to setup more items manually that what is provided for you in the above samples:
25 |
26 | - [Quickstart: Deploy a Python (Django or Flask) web app to Azure App Service](https://docs.microsoft.com/azure/app-service/quickstart-python)
27 | - [Deploy a Python (Django or Flask) web app with PostgreSQL in Azure](https://docs.microsoft.com/azure/app-service/tutorial-python-postgresql-app)
28 |
29 | ## Setup
30 |
31 | This procedure configures your Python environment to send telemetry to the Application Insights feature of the Azure Monitor service with the OpenCensus Azure monitor exporters. It works with any Python application on-premises or in the cloud.
32 |
33 | ## Prerequisites
34 |
35 | To get started with the following samples, you need to:
36 |
37 | - Install Python (all of the samples have been tested to work with version 3.9.x and the Azure resources are deployed using 3.9).
38 | - Install the latest version of Visual Studio Code with the following extensions.
39 | - [ms-python.python](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
40 | - [ms-azuretools.vscode-azurefunctions](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions)
41 | - Install [Azure Functions Core Tools](https://docs.microsoft.com/azure/azure-functions/functions-run-local?tabs=v4%2Cwindows%2Ccsharp%2Cportal%2Cbash#install-the-azure-functions-core-tools)
42 | - Create a [free Azure account](https://azure.microsoft.com/free/) if you don't already have an Azure subscription.
43 | - Install [Azure Storage Emulator](https://docs.microsoft.com/azure/storage/common/storage-use-emulator) (for local Function App testing)
44 |
45 | ## Deploy the environment
46 |
47 | - Open a Windows PowerShell window.
48 | - Run the following:
49 |
50 | ```Powershell
51 | cd $home;
52 |
53 | rm -r python-appinsights -f
54 |
55 | git clone https://github.com/Azure-Samples/azure-monitor-opencensus-python python-appinsights
56 | ```
57 |
58 | - Open Visual Studio Code to the cloned repositiory.
59 | - Open a new Windows PowerShell window, run the following:
60 |
61 | ```powershell
62 | cd azure_monitor/artifacts
63 |
64 | setup.ps1
65 | ```
66 |
67 | - This will deploy the ARM template that contains all the necesary resources needed to run these samples.
68 | - Reference the `template.json` file for the ARM template that is deployed. It contains the following resources:
69 |
70 | 
71 |
72 | - It will also update the environment files for the sample files with the specific connection string details from the deployment to speed up your sample exploration.
73 | - Additionally it will download sample files from the [OpenCensus Python](https://github.com/census-instrumentation/opencensus-python) repo.
74 |
75 | ## Configure the environment (Azure)
76 |
77 | - In the Azure Portal, browse to the `python-insights-SUFFIX` Azure Database for SQL.
78 | - Under **Security**, select **Networking**.
79 | - Under **Firewall rules**, select to **Add your client IPv4 address(xx.xx.xx.xx)**.
80 | - Check the **Allow Azure services and resources to access this server**.
81 | - Select **Save**.
82 |
83 | - Setup local MySQL connectivity
84 | - Browse to the Azure Portal.
85 | - Select your lab subscription and resource group.
86 | - Select the **python-appinsights-SUFFIX-mysql** MySQL resource.
87 | - Under **Settings**, select **Connection security**.
88 | - Select the **Yes** toggle for the **Allow access to Azure Services**.
89 | - Select **Add current client IP address**.
90 | - Select **Save**.
91 |
92 | - Setup local PostgresSQL connectivity
93 | - Browse to the Azure Portal.
94 | - Select your lab subscription and resource group.
95 | - Select the **python-appinsights-SUFFIX-mysql** PostgreSQL resource.
96 | - Under **Settings**, select **Connection security**.
97 | - Select the **Yes** toggle for the **Allow access to Azure Services**.
98 | - Select **Add client IP**.
99 | - Select **Save**.
100 |
101 | ## Configure the environment (Visual Studio Code)
102 |
103 | - Open Visual Studio code, select the **Extensions** tab.
104 | - Ensure the following extensions are installed:
105 | - [ms-python.python](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
106 | - [ms-azuretools.vscode-azurefunctions](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions)
107 |
108 | ## Check your Python Version
109 |
110 | - In Visual Studio Code, open a Terminal window, run the following command:
111 |
112 | ```powershell
113 | python.exe --version
114 | ```
115 |
116 | - For Python, make sure you see version 3.9.x. If you do not, do the following:
117 | - Download Python and change your path variables to point to the proper version.
118 | - Restart Visual Studio Code.
119 | - Open the command palette by selecting **View->Command Pallet**.
120 | - Run the `Python: Select interperter` command.
121 | - Select the `Python 3.9.x` version.
122 |
123 | ## Create a Python environment
124 |
125 | - Switch to the terminal window, ensure you are in the `azure_monitor` folder
126 | - Run the following command to create an environment where you can load all the dependencies:
127 |
128 | ```powershell
129 | py -3 -m venv .venv
130 | .venv\scripts\activate
131 | ```
132 |
133 | - If prompted, select **yes**.
134 | - Ensure that you select the new environment in the interpertor selection otherwise the python commands you run later may not map to the proper python version.
135 |
136 | ## Install OpenCensus
137 |
138 | [OpenCensus](https://opencensus.io/) is a set of open source libraries hosted on [GitHub](https://github.com/census-instrumentation) that are used to capture trace and metric data from applications.
139 |
140 | Metrics can be things like latency, HTTP request and reponse lengths whereas distributed tracing shows how a request moves through your application layers and other services. Log data represents user generated logs that can be enriched with context and custom dimensions. Graphs can be built to show how an application or service is performing based on these data points.
141 |
142 | To install OpenCensus:
143 |
144 | - Open a terminal window, run the following:
145 |
146 | ```powershell
147 | python -m pip install --upgrade pip
148 |
149 | python -m pip install opencensus
150 | ```
151 |
152 | ## Install Azure exporter
153 |
154 | OpenCensus needs an exporter to know how to send the log, metric and tracing data to a specific backend. Azure provides an exporter for Azure Monitor and Log Analytics.
155 |
156 | To install the Azure monitor exporter package:
157 |
158 | - Open a terminal window, run the following:
159 |
160 | ```powershell
161 | python -m pip install opencensus-ext-logging
162 | python -m pip install opencensus-ext-azure
163 | python -m pip install opencensus-extension-azure-functions
164 | python -m pip install opencensus-ext-requests
165 | python -m pip install pyodbc
166 | python -m pip install psutil
167 | ```
168 |
169 | ## Configure environment variables
170 |
171 | - Open the `.\azure_monitor\.env` file notice that as part of the setup the values have been copied into the environment file.
172 | - Browse to the Azure Portal.
173 | - Select the Application Insights **python-appinsights-SUFFIX** resource.
174 | - On the **Overview** page, copy the connection string. Be sure to verify that the values match in the `.env` file
175 |
--------------------------------------------------------------------------------
/azure_monitor/artifacts/common.ps1:
--------------------------------------------------------------------------------
1 | function GetSuffix()
2 | {
3 | $suffix = get-content "suffix.txt" -ea SilentlyContinue;
4 |
5 | if (!$suffix)
6 | {
7 | $suffix = -join ((65..90) + (97..122) | Get-Random -Count 5 | % {[char]$_});
8 |
9 | add-content "suffix.txt" $suffix;
10 | }
11 |
12 | return $suffix.tolower();
13 | }
14 | function SelectSubscription()
15 | {
16 | #select a subscription
17 | $subs = Get-AzSubscription | Select-Object
18 | if($subs.GetType().IsArray -and $subs.length -gt 1){
19 | Write-Host "You have multiple Azure subscriptions - please select the one you want to use:"
20 | for($i = 0; $i -lt $subs.length; $i++)
21 | {
22 | Write-Host "[$($i)]: $($subs[$i].Name) (ID = $($subs[$i].Id))"
23 | }
24 | $selectedIndex = -1
25 | $selectedValidIndex = 0
26 | while ($selectedValidIndex -ne 1)
27 | {
28 | $enteredValue = Read-Host("Enter 0 to $($subs.Length - 1)")
29 | if (-not ([string]::IsNullOrEmpty($enteredValue)))
30 | {
31 | if ([int]$enteredValue -in (0..$($subs.Length - 1)))
32 | {
33 | $selectedIndex = [int]$enteredValue
34 | $selectedValidIndex = 1
35 | }
36 | else
37 | {
38 | Write-Output "Please enter a valid subscription number."
39 | }
40 | }
41 | else
42 | {
43 | Write-Output "Please enter a valid subscription number."
44 | }
45 | }
46 | $selectedSub = $subs[$selectedIndex].Id
47 | Select-AzSubscription -SubscriptionId $selectedSub
48 | az account set --subscription $selectedSub
49 | }
50 | }
--------------------------------------------------------------------------------
/azure_monitor/artifacts/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "suffix": {
6 | "value": "GET-SUFFIX"
7 | },
8 | "administratorLogin": {
9 | "value": "wsuser"
10 | },
11 | "administratorLoginPassword": {
12 | "value": "Microsoft123"
13 | },
14 | "vmSize": {
15 | "value": "Standard_E2s_v3"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/azure_monitor/artifacts/setup.ps1:
--------------------------------------------------------------------------------
1 | . .\common.ps1
2 |
3 | #deploy the arm template
4 | Connect-AzAccount -UseDeviceAuthentication;
5 |
6 | SelectSubscription;
7 |
8 | $location = "eastus";
9 | $suffix = GetSuffix;
10 | $resourceGroupName = "python-appinsights-$suffix";
11 |
12 | $subscriptionId = (Get-AzContext).Subscription.Id
13 | $tenantId = (Get-AzContext).Tenant.Id
14 | $global:logindomain = (Get-AzContext).Tenant.Id;
15 |
16 | #create the resource group
17 | New-AzResourceGroup -Name $resourceGroupName -Location $location -force;
18 |
19 | #run the deployment...
20 | $templatesFile = "./template.json"
21 | $parametersFile = "./parameters.json"
22 |
23 | $content = Get-Content -Path $parametersFile -raw;
24 | $content = $content.Replace("GET-SUFFIX",$suffix);
25 | $content | Set-Content -Path "$($parametersFile).json";
26 |
27 | $results = New-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile $templatesFile -TemplateParameterFile "$($parametersFile).json";
28 |
29 | #get the values
30 | $appInsights = Get-AzApplicationInsights -ResourceGroupName $resourceGroupName -Name "python-appinsights-$suffix"
31 |
32 | $dataLakeAccountName = "storage$suffix";
33 | $dataLakeStorageUrl = "https://"+ $dataLakeAccountName + ".dfs.core.windows.net/"
34 | $dataLakeStorageBlobUrl = "https://"+ $dataLakeAccountName + ".blob.core.windows.net/"
35 | $dataLakeStorageAccountKey = (Get-AzStorageAccountKey -ResourceGroupName $resourceGroupName -AccountName $dataLakeAccountName)[0].Value
36 | $dataLakeContext = New-AzStorageContext -StorageAccountName $dataLakeAccountName -StorageAccountKey $dataLakeStorageAccountKey
37 |
38 | $content = get-content "../env.template"
39 | $content = $content.replace("{INSIGHTS_KEY}",$appInsights.InstrumentationKey);
40 | $content = $content.replace("{INSIGHTS_CONNECTION_STRING}",$appInsights.ConnectionString);
41 | $content = $content.replace("{SUFFIX}",$suffix);
42 | $content = $content.replace("{DBUSER}","wsuser");
43 | $content = $content.replace("{DBPASSWORD}","Microsoft123");
44 | $content = $content.replace("{STORAGE_CONNECTION_STRING}",$dataLakeContext.ConnectionString);
45 | #$content = $content.replace("{FUNCTION_URL}","");
46 | set-content "../.env" $content;
47 |
48 | $content = get-content "../django_sample/.env.example"
49 | $content = $content.replace("{INSIGHTS_KEY}",$appInsights.InstrumentationKey);
50 | $content = $content.replace("{INSIGHTS_CONNECTION_STRING}",$appInsights.ConnectionString);
51 | $content = $content.replace("{SUFFIX}",$suffix);
52 | $content = $content.replace("{DBUSER}","wsuser");
53 | $content = $content.replace("{DBPASSWORD}","Microsoft123");
54 | $content = $content.replace("{STORAGE_CONNECTION_STRING}",$dataLakeContext.ConnectionString);
55 | #$content = $content.replace("{FUNCTION_URL}","");
56 | set-content "../django_sample/.env" $content;
57 |
58 | $content = get-content "../azfunc_sample/local.settings.json"
59 | $content = $content.replace("{INSIGHTS_KEY}",$appInsights.InstrumentationKey);
60 | $content = $content.replace("{INSIGHTS_CONNECTION_STRING}",$appInsights.ConnectionString);
61 | $content = $content.replace("{SUFFIX}",$suffix);
62 | $content = $content.replace("{DBUSER}","wsuser");
63 | $content = $content.replace("{DBPASSWORD}","Microsoft123");
64 | $content = $content.replace("{STORAGE_CONNECTION_STRING}",$dataLakeContext.ConnectionString);
65 | #$content = $content.replace("{FUNCTION_URL}","");
66 | set-content "../azfunc_sample/local.settings.json" $content;
67 |
68 | #copy over the opencensus git repo...
69 | #download the git repo...
70 | Write-Host "Download Git repo." -ForegroundColor Green -Verbose
71 | git clone https://github.com/census-instrumentation/opencensus-python opencensus-python
72 |
73 | #copy the example files to the "simple_sample" folder
74 | copy ./opencensus-python/contrib/opencensus-ext-azure/examples/*/*.py ../simple_sample
75 |
76 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/BaltimoreCyberTrustRoot.crt.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
3 | RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
4 | VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
5 | DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
6 | ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
7 | VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
8 | mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
9 | IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
10 | mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
11 | XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
12 | dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
13 | jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
14 | BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
15 | DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
16 | 9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
17 | jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
18 | Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
19 | ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
20 | R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
21 | -----END CERTIFICATE-----
22 |
23 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/DigiCertGlobalRootCA.crt.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
4 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
5 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
6 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
7 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
9 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
10 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
11 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
12 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
13 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
14 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
15 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
16 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
17 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
18 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
19 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
20 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
21 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
22 | -----END CERTIFICATE-----
23 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/MyHttpFunction/__init__.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import azure.functions as func
3 | import mysql.connector
4 | import ssl
5 | import os, uuid
6 | import pathlib
7 |
8 | from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient, __version__
9 | from opencensus.extension.azure.functions import OpenCensusExtension
10 | from opencensus.trace import config_integration
11 |
12 | config_integration.trace_integrations(['mysql'])
13 | config_integration.trace_integrations(['requests'])
14 |
15 | OpenCensusExtension.configure()
16 |
17 | def main(myHttpFunction: func.HttpRequest) -> func.HttpResponse:
18 |
19 | logging.info('Python HTTP trigger function processed a request.')
20 |
21 | #sql server
22 | #server = os.getenv('DB_SERVER')
23 | #database = os.getenv('DB_DATABASE')
24 | #username = os.getenv('DB_USERNAME')
25 | #password = os.getenv('DB_PASSWORD')
26 | #driver= '{ODBC Driver 17 for SQL Server}'
27 |
28 | server = os.getenv('DBHOST')
29 | database = os.getenv('DBNAME')
30 | username = os.getenv('DBUSER')
31 | password = os.getenv('DBPASS')
32 |
33 | crtpath = 'BaltimoreCyberTrustRoot.crt.pem'
34 | #crtpath = 'DigiCertGlobalRootCA.crt.pem'
35 |
36 | ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
37 |
38 | # Connect to MySQL
39 | cnx = mysql.connector.connect(
40 | user=username,
41 | password=password,
42 | host=server,
43 | port=3306,
44 | ssl_ca=crtpath,
45 | tls_versions=['TLSv1.2']
46 | )
47 |
48 | logging.info(cnx)
49 |
50 | # Show databases
51 | cursor = cnx.cursor()
52 | cursor.execute("SHOW DATABASES")
53 | result_list = cursor.fetchall()
54 |
55 | # Build result response text
56 | result_str_list = []
57 | for row in result_list:
58 | row_str = ', '.join([str(v) for v in row])
59 | result_str_list.append(row_str)
60 | result_str = '\n'.join(result_str_list)
61 |
62 | #add a random file to storage account...
63 | connect_str = os.getenv('AZURE_STORAGE_CONNECTION_STRING')
64 |
65 | # Create the BlobServiceClient object which will be used to create a container client
66 | blob_service_client = BlobServiceClient.from_connection_string(connect_str)
67 |
68 | # Create a unique name for the container
69 | container_name = 'files'
70 |
71 | # Create the container
72 | try:
73 | container_client = blob_service_client.create_container(container_name)
74 | except:
75 | logging.info("Container exists?")
76 |
77 | # Create a local directory to hold blob data
78 | ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
79 |
80 | logging.info(ROOT_DIR)
81 |
82 | local_path = ROOT_DIR + "\\tmp\data"
83 |
84 | try:
85 | os.mkdir(local_path)
86 | except:
87 | logging.info("Folder exists?")
88 |
89 | # Create a file in the local data directory to upload and download
90 | local_file_name = str(uuid.uuid4()) + ".txt"
91 | upload_file_path = os.path.join(local_path, local_file_name)
92 |
93 | # Write text to the file
94 | file = open(upload_file_path, 'w')
95 | file.write("Hello, World!")
96 | file.close()
97 |
98 | # Create a blob client using the local file name as the name for the blob
99 | blob_client = blob_service_client.get_blob_client(container=container_name, blob=local_file_name)
100 |
101 | logging.info("\nUploading to Azure Storage as blob:\n\t" + local_file_name)
102 |
103 | # Upload the created file
104 | with open(upload_file_path, "rb") as data:
105 | blob_client.upload_blob(data)
106 |
107 |
108 | return func.HttpResponse(
109 | result_str,
110 | status_code=200
111 | )
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/MyHttpFunction/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "__init__.py",
3 | "bindings": [
4 | {
5 | "authLevel": "function",
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "myHttpFunction",
9 | "methods": [
10 | "get",
11 | "post"
12 | ]
13 | },
14 | {
15 | "type": "http",
16 | "direction": "out",
17 | "name": "$return"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/MyHttpFunction/sample.dat:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Azure"
3 | }
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Sample: Configuring Function Apps with Python and OpenCensus'
3 | description: Create Azure Functions using Python and OpenCensus.
4 | author: givenscj
5 | ms.author: givenscj
6 | ms.devlang: python
7 | ms.topic: sample
8 | ms.date: 07/27/2022
9 | ms.custom: []
10 | ---
11 |
12 | # Sample: Configuring Function Apps with OpenCensus SDK
13 |
14 | This is an example of how to configure Azure Function applications to send dependencies calls to Azure Monitor. You will run the samples both locally and deploy the functions to an Azure Functions resource you deployed as part of the setup script. These Function Apps sample will utilize OpenCensus depencencies to record calls to external services such as internal method calls, MySQL and Azure Storage.
15 |
16 | ## 1 - Environment Setup
17 |
18 | Follow the steps in the [documentation](/azure_monitor/README.md) to setup your environment.
19 |
20 | ## 2 - Utilize Function Apps with OpenCensus
21 |
22 | - Start the Azure Storage Emulator.
23 | - Switch to Visual Studio Code.
24 | - Open a terminal window, run the following commands to start the Python functions locally:
25 |
26 | ```powershell
27 | cd .\azure_monitor\azfunc_sample
28 |
29 | python -m pip install -r requirements.txt
30 |
31 | func start
32 | ```
33 |
34 | > NOTE: The `func` command is installed as part of the Azure Function Tools as part of the environment setup.
35 |
36 | - If you get an error about `psutil._psutil_windows` do the following:
37 | - Open the `.venv\Lib\site-packages` folder and delete the `psutil` and `psutil-5.9.1.dist-info` folders.
38 | - Then run the following:
39 |
40 | ```Python
41 | python -m pip install --upgrade pip
42 | python -m pip install psutil
43 | ```
44 |
45 | - Browse to the HTTP function endpoint `http://localhost:7071/api/MyHttpFunction`, you can review the code in the `\azfunc_sample\MyHttpFunction\__init__.py`, you will see that it performs the following:
46 | - Connects to MySQL and performs a query
47 | - Creates a file an uploads it to Azure Storage
48 |
49 | ## 3 - Deploy to Azure
50 |
51 | - Run the following command to deploy the function apps to Azure. Be sure to replace the `SUFFIX` below (you can find it in the deployed Azure Portal resource group or in the `.\azure_monitor\artifacts\suffix.txt` file that was generated by the setup script):
52 |
53 | ```powershell
54 | cd .\azure_monitor\azfunc_sample
55 |
56 | az login
57 |
58 | func azure functionapp publish python-appinsights-SUFFIX
59 | ```
60 |
61 | - You should see the function applications deployed successfully:
62 |
63 | 
64 |
65 | - Switch to the Azure Portal.
66 | - Navigate to the **python-appinsights-SUFFIX** function app.
67 | - Under **Functions**, select **Functions**, you should see the two python function apps deployed.
68 |
69 | > **NOTE** Azure Function runtimes support various Python versions depending on the version selected. See [Languages by runtime version](https://docs.microsoft.com/azure/azure-functions/supported-languages#languages-by-runtime-version).
70 |
71 | - Review the function app code. Notice the usage of the a storage account and a call to the database.
72 |
73 | ## 4 - Review Dependency Log Data
74 |
75 | - Navigate to the **python-appinsights-SUFFIX** Application Insights resource, under **Monitoring**, select **Logs**
76 | - Run the following query:
77 |
78 | ```kusto
79 | dependencies
80 | ```
81 |
82 | - You should see the dependencies displayed for the calls to MySQL and Azure storage displayed.
83 |
84 | 
85 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/docs/assets/01-ai-correlated-invocation.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/azfunc_sample/docs/assets/01-ai-correlated-invocation.PNG
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/docs/assets/02-ai-correlated-invocation.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/azfunc_sample/docs/assets/02-ai-correlated-invocation.PNG
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/docs/assets/03-ai-application-map.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/azfunc_sample/docs/assets/03-ai-application-map.PNG
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "extensionBundle": {
4 | "id": "Microsoft.Azure.Functions.ExtensionBundle",
5 | "version": "[2.*, 3.0.0)"
6 | }
7 | }
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/instrumentation/__init__.py:
--------------------------------------------------------------------------------
1 | # the code in globals ensures that logging
2 | # is initialized at the very beginning
3 | from . import globals # noqa: F401
4 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/instrumentation/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "scriptFile": "main.py",
3 | "bindings": [
4 | {
5 | "authLevel": "anonymous",
6 | "type": "httpTrigger",
7 | "direction": "in",
8 | "name": "req",
9 | "methods": [
10 | "get"
11 | ]
12 | },
13 | {
14 | "type": "http",
15 | "direction": "out",
16 | "name": "$return"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/instrumentation/globals.py:
--------------------------------------------------------------------------------
1 | import os
2 | import socket
3 | import logging
4 |
5 | from logging import Logger
6 | from opencensus.trace import config_integration
7 | from opencensus.ext.azure.log_exporter import AzureLogHandler
8 |
9 |
10 | AI_CONNECTION_STRING = os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING")
11 |
12 | if not AI_CONNECTION_STRING:
13 | raise EnvironmentError(
14 | "AI Connection string not set. Set it through the environment variable: APPLICATIONINSIGHTS_CONNECTION_STRING."
15 | )
16 |
17 | WEBSITE_SITE_NAME = os.getenv("WEBSITE_SITE_NAME")
18 |
19 | if not WEBSITE_SITE_NAME:
20 | raise EnvironmentError(
21 | "Website site name not set. Set it through the environment variable: WEBSITE_SITE_NAME."
22 | )
23 |
24 | EXTERNAL_DEPENDENCY_URL = os.getenv("EXTERNAL_DEPENDENCY_URL")
25 |
26 | if not EXTERNAL_DEPENDENCY_URL:
27 | raise EnvironmentError(
28 | "External dependency URL not set. Set it through the environment variable: EXTERNAL_DEPENDENCY_URL."
29 | )
30 |
31 | # initialize observability (with opencensus)
32 | config_integration.trace_integrations(["logging", "requests"])
33 |
34 |
35 | def callback_add_role_name(envelope):
36 | """Add role name for logger."""
37 | envelope.tags["ai.cloud.role"] = WEBSITE_SITE_NAME
38 | envelope.tags["ai.cloud.roleInstance"] = socket.getfqdn()
39 |
40 |
41 | def getLogger(
42 | name: str,
43 | instrumentation_conn_string: str = AI_CONNECTION_STRING,
44 | propagate: bool = False,
45 | ) -> Logger:
46 | """Get a new logging instance with a handler to send logs to Application Insights
47 |
48 | Args:
49 | name([str]): [The name of the logger]
50 | instrumentation_conn_string([str]): [The AppInsights instrumentation connection string]
51 | propagate([bool]): [Enable log propagation (default: false)]
52 | """
53 | logHandler = AzureLogHandler(connection_string=instrumentation_conn_string)
54 | logHandler.add_telemetry_processor(callback_add_role_name)
55 |
56 | logger = logging.getLogger(name)
57 | logger.addHandler(logHandler)
58 | logger.propagate = propagate
59 |
60 | return logger
61 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/instrumentation/instrumentation_func.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 |
3 | from .globals import getLogger, EXTERNAL_DEPENDENCY_URL
4 | from .utils import call_internal_api, call_external_api
5 |
6 | logger = getLogger(__name__)
7 |
8 |
9 | class FunctionLogic:
10 | @classmethod
11 | def run(cls, req: func.HttpRequest) -> func.HttpResponse:
12 | """Azure Function business logic
13 |
14 | Args:
15 | req([HttpRequest]): [Incoming HTTP request]
16 | """
17 | logger.info("new invocation received")
18 |
19 | # TRACK DEPENDENCY (IN-PROC):
20 | # This long running code is logged as a dependency
21 | # it uses a method decorator
22 | call_internal_api(delay=3.0)
23 |
24 | # TRACK DEPENDENCY (HTTP):
25 | # This uses Opencensus' requests extension
26 | call_external_api(url=EXTERNAL_DEPENDENCY_URL)
27 |
28 | # TRACES (SEVERITY)
29 | # create log entries with different severity levels (warning, exception)
30 | logger.warning("log warning message")
31 |
32 | try:
33 | assert 1 == 0
34 | except Exception as ex:
35 | logger.exception(ex)
36 |
37 | return func.HttpResponse(
38 | "This HTTP triggered function executed successfully.",
39 | status_code=200,
40 | )
41 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/instrumentation/main.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 | from opencensus.extension.azure.functions import OpenCensusExtension
3 |
4 | from .globals import AI_CONNECTION_STRING, callback_add_role_name, getLogger
5 | from .instrumentation_func import FunctionLogic
6 |
7 | # configure opencensus ext for azure function.
8 | # this ensures that the an opencensus tracer is created and associated with the func context
9 | OpenCensusExtension.configure(connection_string=AI_CONNECTION_STRING)
10 |
11 | # ensure that dependency records have the correct role name
12 | OpenCensusExtension._exporter.add_telemetry_processor(callback_add_role_name)
13 |
14 | logger = getLogger(__name__)
15 |
16 |
17 | def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
18 | """Azure Function entry point
19 |
20 | Args:
21 | req([HttpRequest]): [Incoming HTTP request]
22 | context([Context]): [Azure function invocation context]
23 | """
24 | logger.info("Python HTTP trigger function processed a request.")
25 |
26 | return FunctionLogic.run(req)
27 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/instrumentation/utils.py:
--------------------------------------------------------------------------------
1 | import time
2 | import requests
3 | from functools import wraps
4 | from opencensus.trace import execution_context
5 | from opencensus.trace.tracer import Tracer
6 |
7 | from .globals import getLogger
8 |
9 | logger = getLogger(__name__)
10 |
11 |
12 | def trace_as_dependency(tracer: Tracer = None, name: str = None):
13 | """trace_as_dependency [method decorator to trace a method invocation as a dependency (in AppInsights)]
14 |
15 | Args:
16 | tracer (Tracer): [Opencensus tracer object used to create the trace record.]
17 | name (str): [Name of the created trace record]
18 |
19 | Returns:
20 | The inner function
21 | """
22 |
23 | def inner_function(method):
24 | @wraps(method)
25 | def wrapper(*args, **kwargs):
26 | trace_name = name if (name is not None) else method.__name__
27 | oc_tracer = (
28 | tracer
29 | if (tracer is not None)
30 | else execution_context.get_opencensus_tracer()
31 | )
32 |
33 | if oc_tracer is not None:
34 | with oc_tracer.span(trace_name):
35 | result = method(*args, **kwargs)
36 | else:
37 | result = method(*args, **kwargs)
38 |
39 | return result
40 |
41 | return wrapper
42 |
43 | return inner_function
44 |
45 |
46 | @trace_as_dependency(name="long running api call")
47 | def call_internal_api(delay: float) -> None:
48 | """Dummy API call to demonstrate dependecy tracking (IN-PROC)
49 |
50 | Args:
51 | delay([float]): [Delay method execution in sec]
52 | """
53 | time.sleep(delay)
54 |
55 |
56 | def call_external_api(url: str) -> None:
57 | """Dummy API call to demonstrate dependecy tracking (HTTP)
58 |
59 | Args:
60 | url([str]): [URL to invoke]
61 | """
62 | requests.get(url)
63 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "FUNCTIONS_WORKER_RUNTIME": "python",
5 | "DBHOST": "python-appinsights-{SUFFIX}-mysql.mysql.database.azure.com",
6 | "DBNAME": "python",
7 | "DBUSER": "{DBUSER}@python-appinsights-{SUFFIX}-mysql",
8 | "DBPASS": "{DBPASSWORD}",
9 | "AZURE_STORAGE_CONNECTION_STRING" : "{STORAGE_CONNECTION_STRING}",
10 | "APPLICATIONINSIGHTS_CONNECTION_STRING" : "{INSIGHTS_CONNECTION_STRING}",
11 | "AzureWebJobsStorage" : "UseDevelopmentStorage=true",
12 | "WEBSITE_SITE_NAME" : "instrumentation",
13 | "EXTERNAL_DEPENDENCY_URL" : "https://www.bing.com"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/requirements.txt:
--------------------------------------------------------------------------------
1 | opencensus
2 | opencensus-ext-logging
3 | opencensus-ext-azure
4 | opencensus-extension-azure-functions
5 | opencensus-ext-requests
6 | opencensus-ext-mysql
7 | azure-functions
8 | requests
9 | mysql
10 | mysql-connector-python
11 | pyodbc
12 | azure-storage-blob
13 | psutil
14 | cffi
15 | ffi
16 | pytest
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/azfunc_sample/tests/__init__.py
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/tests/test_globals.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from opencensus.ext.azure.log_exporter import AzureLogHandler
4 | from opencensus.log import TraceLogger
5 |
6 | from instrumentation.globals import getLogger
7 |
8 | CONNECTION_STRING = "InstrumentationKey=00000000-0000-0000-0000-000000000000"
9 |
10 |
11 | @pytest.mark.parametrize(
12 | "name, conn_string, propagate",
13 | [(__name__, CONNECTION_STRING, False), ("test", CONNECTION_STRING, True)],
14 | )
15 | def test_logger_correctly_configured(
16 | name: str, conn_string: str, propagate: bool
17 | ) -> None:
18 | sut = getLogger(
19 | name=name, instrumentation_conn_string=conn_string, propagate=propagate
20 | )
21 |
22 | assert sut.name == name
23 | assert sut.propagate == propagate
24 | assert isinstance(sut, TraceLogger)
25 | assert len(sut.handlers) == 1
26 | assert isinstance(sut.handlers[0], AzureLogHandler)
27 | assert sut.handlers[0].options["connection_string"] == CONNECTION_STRING
28 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/tests/test_instrumentation_func.py:
--------------------------------------------------------------------------------
1 | import azure.functions as func
2 |
3 | from instrumentation.instrumentation_func import FunctionLogic
4 |
5 |
6 | def create_http_request() -> func.HttpRequest:
7 | # Construct a mock HTTP request.
8 | req = func.HttpRequest(
9 | method="GET",
10 | body="",
11 | url="/api/sample",
12 | )
13 | return req
14 |
15 |
16 | def test_run_successful() -> None:
17 | http_req = create_http_request()
18 | http_resp = FunctionLogic.run(http_req)
19 |
20 | assert http_resp.status_code == 200
21 |
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/tmp/data/05582fa0-2f83-48ca-96ae-8206ee59aab5.txt:
--------------------------------------------------------------------------------
1 | Hello, World!
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/tmp/data/a20f2b98-bd5a-4692-ab93-397a460d722f.txt:
--------------------------------------------------------------------------------
1 | Hello, World!
--------------------------------------------------------------------------------
/azure_monitor/azfunc_sample/tmp/data/bd7ae612-ca6a-4bb4-a146-9926f24a4e10.txt:
--------------------------------------------------------------------------------
1 | Hello, World!
--------------------------------------------------------------------------------
/azure_monitor/django_sample/.env.example:
--------------------------------------------------------------------------------
1 | APPINSIGHTS_INSTRUMENTATIONKEY={INSIGHTS_KEY}
2 | APPLICATIONINSIGHTS_CONNECTION_STRING={INSIGHTS_CONNECTION_STRING}
3 | STORAGE_ACCOUNT_NAME=storage{SUFFIX}
4 |
5 | DB_SERVER=python-appinsights-{SUFFIX}.database.windows.net
6 | DB_DATABASE=python
7 | DB_USERNAME={DBUSER}
8 | DB_PASSWORD={DBPASSWORD}
9 | DB_PORT = 1433
10 |
11 | DBHOST=python-appinsights-{SUFFIX}-mysql.mysql.database.azure.com
12 | DBNAME=python
13 | DBUSER={DBUSER}@python-appinsights-{SUFFIX}-mysql
14 | DBPASS={DBPASSWORD}
15 |
16 | PGDBHOST=python-appinsights-{SUFFIX}-pg.postgres.database.azure.com
17 | PGDBNAME=python
18 | PGDBUSER={DBUSER}@python-appinsights-{SUFFIX}-pg
19 | PGDBPASS={DBPASSWORD}
20 |
21 | AZURE_STORAGE_CONNECTION_STRING={STORAGE_CONNECTION_STRING}
22 | FUNCTION_URL={FUNCTION_URL}
23 |
24 | # Blob storage connection info
25 | STORAGE_URL=https://storage{SUFFIX}.blob.core.windows.net
26 | STORAGE_CONTAINER_NAME=photos
27 |
28 | #WEBSITE_HOSTNAME=http://127.0.0.1:8000
--------------------------------------------------------------------------------
/azure_monitor/django_sample/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv*
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 | __azurite*
113 | .deployment
114 |
115 | # Keys
116 | *.pem
117 |
118 | # Spyder project settings
119 | .spyderproject
120 | .spyproject
121 |
122 | # Rope project settings
123 | .ropeproject
124 |
125 | # mkdocs documentation
126 | /site
127 |
128 | # mypy
129 | .mypy_cache/
130 | .dmypy.json
131 | dmypy.json
132 |
133 | # Pyre type checker
134 | .pyre/
135 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Sample: Configuring Django applications with OpenCensus'
3 | description: Configuring Django applications with OpenCensus.
4 | author: givenscj
5 | ms.author: givenscj
6 | ms.devlang: python
7 | ms.topic: sample
8 | ms.date: 07/27/2022
9 | ms.custom: []
10 | ---
11 |
12 | # Sample: Configuring Django applications with OpenCensus
13 |
14 | This next example shows how to add the various OpenCensus settings to a Django application to send logging and metric data to Azure Monitor. You will run the samples both locally and deploy the Django web application to the Azure App Service resource you deployed as part of the setup script.
15 |
16 | ## 1 - Environment Setup
17 |
18 | Follow the steps in the [setup documentation](/azure_monitor/README.md).
19 |
20 | ## 2 - Configure Environment
21 |
22 | - Run the following commands to setup the environment:
23 |
24 | ```powershell
25 | cd $home
26 |
27 | cd .\azure_monitor\django_sample
28 | ```
29 |
30 | - Review the `requirements.txt` file, you will see all the required packages for the sample included. Exercute the following command to install all the packages in the requirements.txt file:
31 |
32 | ```python
33 | python -m pip install -r requirements.txt
34 | ```
35 |
36 | - The following items have been done for you in the ARM template:
37 | - System assigned managed identity for the app service is enabled.
38 | - Storage Blob Data Contributor has been assigned to the managed identity.
39 |
40 | - Setup local PostgreSQL connectivity
41 | - Browse to the Azure Portal.
42 | - Select your lab subscription and resource group.
43 | - Select the **python-appinsights-SUFFIX-pg** Postgres SQL resource.
44 | - Under **Settings**, select **Connection security**.
45 | - Select **Add client IP**.
46 | - Select **Save**.
47 |
48 | - Setup the Application insights middleware
49 | - Open the `./azure_monitor/django_sample/azureproject/settings.py` file.
50 | - Ensure the following line is present in the `MIDDLEWARE` section:
51 |
52 | ```python
53 | 'opencensus.ext.django.middleware.OpencensusMiddleware',
54 | ```
55 |
56 | -- Ensure the following `OPENCENSUS` section is present in the `settings.py` file.
57 |
58 | ```python
59 | OPENCENSUS = {
60 | 'TRACE': {
61 | 'SAMPLER': 'opencensus.trace.samplers.ProbabilitySampler(rate=1)',
62 | 'EXPORTER': 'opencensus.ext.azure.trace_exporter.AzureExporter(connection_string="' + appKey + '")',
63 | }
64 | }
65 | ```
66 |
67 | ## 3 - Create Database
68 |
69 | - Create the database schema:
70 |
71 | ```python
72 | py manage.py migrate
73 | ```
74 |
75 | - If you get an error about `psutil._psutil_windows` do the following:
76 | - Open the `\azure-monitor\.venv\Lib\site-packages` folder and delete the `psutil` and `psutil-5.9.1.dist-info` folders.
77 | - Then run the following:
78 |
79 | ```Python
80 | python -m pip install --upgrade pip
81 | python -m pip install psutil
82 | ```
83 |
84 | ## 4 - Test Application
85 |
86 | - Run the application and test the logging.
87 |
88 | ```python
89 | python manage.py runserver
90 | ```
91 |
92 | - Open a web browser to `http://127.0.0.1:8000/`.
93 | - Add a new resturant.
94 | - Select **Add new resturant**.
95 | - For the name, type `Contoso BBQ`.
96 | - For the address, type `1 Microsoft Way`.
97 | - For the description, type `BBQ`.
98 | - Select **Submit**, the resturant will be created.
99 | - Select **Add new review**.
100 | - Type a name.
101 | - Select a rating.
102 | - Add a comment.
103 | - Choose a random picture.
104 | - Select **Save changes**.
105 |
106 | ## 4 - Review Log Data
107 |
108 | - Switch to the Azure Portal.
109 | - Browse to the **python-applicationinsights-SUFFIX** Application Insights resource.
110 | - Under **Monitoring**, select **Logs**.
111 | - In the query window, type the following:
112 |
113 | ```kql
114 | requests
115 | | where cloud_RoleName == "manage.py"
116 | ```
117 |
118 | - Select **Run**, you should see the following:
119 |
120 | 
121 |
122 | - Select the **Azure** icon in the toolbar of Visual Studio code.
123 | - If prompted, select **Sign in to Azure..** and follow the prompts.
124 | - Expand **Resources**, if needed, sign-in to Azure.
125 | - Expand your target lab subscription.
126 | - Expand **App Services**.
127 | - Locate the target web app to deploy too, this will be **python-appinsights-SUFFIX-app**.
128 | - If needed, select **Install extension to enable additional features**, then select **Install** to instal the Azure App Service extension.
129 | - In Visual Studio Code, right-click the **django_sample** folder, select **Deploy to web app**.
130 | - Selec the **python-appinsights-SUFFIX-app** web app.
131 | - If prompted, select **Deploy**.
132 | - Browse to the **python-appinsights-SUFFIX-app.azurewebsites.net** web site, the site should load successfully.
133 | - Add some more resturants and reviews, this will cause log data to be sent to Azure Monitor via OpenCensus.
134 |
135 | ## 5 - Review Custom Metrics
136 |
137 | For the sample resturant application, you have two places where metrics are sent to Application Insights.
138 |
139 | - Single Value - The number of resturants created.
140 | - Aggregation - Page views over a one minute time period.
141 |
142 | - Switch to the Azure Portal and the application insights instance.
143 | - Under **Monitoring**, select **Metrics**.
144 | - For the **Metric namespace**, select **reviews_view**.
145 | - For the **Metric**, select **reviews_view**.
146 | - You should see some data displayed for the reviews made from the web site:
147 |
148 | 
149 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/azureproject/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/django_sample/azureproject/__init__.py
--------------------------------------------------------------------------------
/azure_monitor/django_sample/azureproject/app_insights.py:
--------------------------------------------------------------------------------
1 | import os
2 | import logging
3 |
4 | from opencensus.ext.azure.log_exporter import AzureLogHandler
5 | from opencensus.ext.azure import metrics_exporter
6 | from opencensus.stats import aggregation as aggregation_module
7 | from opencensus.stats import measure as measure_module
8 | from opencensus.stats import stats as stats_module
9 | from opencensus.stats import view as view_module
10 | from opencensus.tags import tag_map as tag_map_module
11 |
12 | stats = stats_module.stats
13 | view_manager = stats.view_manager
14 | stats_recorder = stats.stats_recorder
15 |
16 | appKey = os.getenv('APPLICATIONINSIGHTS_CONNECTION_STRING')
17 |
18 | REST_MEASURE = measure_module.MeasureInt("resturants",
19 | "number of resturants",
20 | "resturants")
21 | REST_VIEW = view_module.View("rest_view",
22 | "number of resturants",
23 | ["state"],
24 | REST_MEASURE,
25 | aggregation_module.CountAggregation())
26 |
27 | ORDERS_MEASURE = measure_module.MeasureInt("orders",
28 | "number of orders",
29 | "orders")
30 |
31 | ORDERS_VIEW = view_module.View("orders_view",
32 | "number of orders",
33 | [],
34 | ORDERS_MEASURE,
35 | aggregation_module.CountAggregation())
36 |
37 | REVIEWS_MEASURE = measure_module.MeasureInt("reviews","number of reviews","reviews")
38 |
39 | REVIEWS_VIEW = view_module.View("reviews_view",
40 | "number of reviews",
41 | ["resturantId"],
42 | REVIEWS_MEASURE,
43 | aggregation_module.CountAggregation())
44 |
45 | logger = logging.getLogger(__name__)
46 |
47 | logger.addHandler(AzureLogHandler(
48 | connection_string=appKey)
49 | )
50 |
51 | def register_views():
52 |
53 | stats = stats_module.stats
54 | view_manager = stats.view_manager
55 |
56 | exporter = metrics_exporter.new_metrics_exporter(connection_string=appKey)
57 | view_manager.register_exporter(exporter)
58 |
59 | view_manager.register_view(REVIEWS_VIEW)
60 | view_manager.register_view(ORDERS_VIEW)
61 | view_manager.register_view(REST_VIEW)
62 |
63 | def record_metric_pageviews():
64 | mmap = stats_recorder.new_measurement_map()
65 | tmap = tag_map_module.TagMap()
66 | mmap.measure_int_put(REVIEWS_MEASURE, 1)
67 | mmap.record(tmap)
68 | logger.info("metrics: %s value: %s number of measurements: %s ",REVIEWS_MEASURE.name, 1, len(mmap.measurement_map))
69 |
70 | def record_metric_review(tmap):
71 | mmap = stats_recorder.new_measurement_map()
72 | mmap.measure_int_put(REVIEWS_MEASURE, 1)
73 | mmap.record(tmap)
74 | logger.info("metrics: %s value: %s number of measurements: %s ",REVIEWS_MEASURE.name, 1, len(mmap.measurement_map))
75 |
76 | def record_metric_resturant(tmap):
77 | mmap = stats_recorder.new_measurement_map()
78 | mmap.measure_int_put(REST_MEASURE, 1)
79 | mmap.record(tmap)
80 | logger.info("metrics: %s value: %s number of measurements: %s ",REVIEWS_MEASURE.name, 1, len(mmap.measurement_map))
81 |
82 | def record_metric_order():
83 | mmap = stats_recorder.new_measurement_map()
84 | tmap = tag_map_module.TagMap()
85 | mmap.measure_int_put(ORDERS_MEASURE, 1)
86 | mmap.record(tmap)
87 | logger.info("metrics: %s value: %s number of measurements: %s ",REVIEWS_MEASURE.name, 1, len(mmap.measurement_map))
88 |
89 | def record_metric_float(mmap,value,measure):
90 | # data from the speed test
91 | mmap.measure_float_put(measure,value)
92 | # the measure becomes the key to the measurement map
93 | logger.info("metrics: %s value: %s number of measurements: %s ",measure.name, value, len(mmap.measurement_map))
94 |
95 |
96 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/azureproject/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for azureproject project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'azureproject.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/azureproject/development.py:
--------------------------------------------------------------------------------
1 | import os
2 | from .settings import *
3 | from .get_token import get_token
4 | from azureproject.app_insights import *
5 | from opencensus.trace import config_integration
6 |
7 | config_integration.trace_integrations(['postgresql'])
8 | config_integration.trace_integrations(['requests'])
9 |
10 | # SECURITY WARNING: don't run with debug turned on in production!
11 | DEBUG = True
12 |
13 | # Don't use Whitenoise to avoid having to run collectstatic first.
14 | STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
15 |
16 | ALLOWED_HOSTS = ['*']
17 |
18 | # Configure Postgres database for local development
19 | # Set these environment variables in the .env file for this project.
20 | DATABASES = {
21 | 'default': {
22 | 'ENGINE': 'django.db.backends.postgresql',
23 | 'NAME': os.environ['PGDBNAME'],
24 | 'HOST': os.environ['PGDBHOST'],
25 | 'USER': os.environ['PGDBUSER'],
26 | 'OPTIONS': {'sslmode': 'require'},
27 | 'PASSWORD': os.environ['PGDBPASS']
28 | }
29 | }
30 |
31 | #load all the custom metrics...
32 | register_views()
33 |
34 | #get_token()
35 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/azureproject/get_token.py:
--------------------------------------------------------------------------------
1 | import os
2 | from azure.identity import DefaultAzureCredential
3 | import django.conf as conf
4 |
5 | def get_token():
6 | if 'WEBSITE_HOSTNAME' in os.environ:
7 | # Azure hosted, refresh token that becomes password.
8 | azure_credential = DefaultAzureCredential()
9 | # Get token for Azure Database for PostgreSQL
10 | token = azure_credential.get_token("https://ossrdbms-aad.database.windows.net")
11 | #conf.settings.DATABASES['default']['PASSWORD'] = token.token
12 | conf.settings.DATABASES['default']['PASSWORD'] = os.environ['DBPASS']
13 | else:
14 | # Locally, read password from environment variable.
15 | conf.settings.DATABASES['default']['PASSWORD'] = os.environ['DBPASS']
16 | print("Read password env variable.")
17 | return
--------------------------------------------------------------------------------
/azure_monitor/django_sample/azureproject/production.py:
--------------------------------------------------------------------------------
1 | import os
2 | from .settings import *
3 | from .get_token import get_token
4 | from azureproject.app_insights import *
5 | from opencensus.trace import config_integration
6 |
7 | config_integration.trace_integrations(['postgresql'])
8 | config_integration.trace_integrations(['requests'])
9 |
10 | # Configure the domain name using the environment variable
11 | # that Azure automatically creates for us.
12 | ALLOWED_HOSTS = [os.environ['WEBSITE_HOSTNAME']] if 'WEBSITE_HOSTNAME' in os.environ else []
13 |
14 | CSRF_TRUSTED_ORIGINS = ['https://'+ os.environ['WEBSITE_HOSTNAME']] if 'WEBSITE_HOSTNAME' in os.environ else []
15 | DEBUG = False
16 | DEBUG_PROPAGATE_EXCEPTIONS = True
17 |
18 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage'
19 |
20 | # DBHOST is only the server name, not the full URL
21 | #hostname = os.environ['DBHOST']
22 | #username = os.environ['DBUSER'] + "@" + os.environ['DBHOST']
23 |
24 | # Configure Postgres database; the full username for PostgreSQL flexible server is
25 | # username (not @sever-name).
26 | DATABASES = {
27 | 'default': {
28 | 'ENGINE': 'django.db.backends.postgresql',
29 | 'NAME': os.environ['PGDBNAME'],
30 | 'HOST': os.environ['PGDBHOST'],
31 | 'USER': os.environ['PGDBUSER'],
32 | 'OPTIONS': {'sslmode': 'require'},
33 | 'PASSWORD': os.environ['PGDBPASS']
34 | }
35 | }
36 |
37 | #load all the custom metrics...
38 | register_views()
39 |
40 | #get_token()
41 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/azureproject/settings.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import os
3 |
4 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
5 | BASE_DIR = Path(__file__).resolve().parent.parent
6 |
7 | appKey = os.getenv('APPLICATIONINSIGHTS_CONNECTION_STRING')
8 |
9 | # SECURITY WARNING: keep the secret key used in production secret!
10 | SECRET_KEY = 'django-insecure-7ppocbnx@w71dcuinn*t^_mzal(t@o01v3fee27g%rg18fc5d@'
11 |
12 | # Application definition
13 | INSTALLED_APPS = [
14 | 'restaurant_review.apps.RestaurantReviewConfig',
15 | 'django.contrib.admin',
16 | 'django.contrib.auth',
17 | 'django.contrib.contenttypes',
18 | 'django.contrib.sessions',
19 | 'django.contrib.messages',
20 | 'whitenoise.runserver_nostatic',
21 | 'django.contrib.staticfiles',
22 | 'sslserver'
23 | ]
24 |
25 | MIDDLEWARE = [
26 | 'django.middleware.security.SecurityMiddleware',
27 | 'whitenoise.middleware.WhiteNoiseMiddleware',
28 | 'django.contrib.sessions.middleware.SessionMiddleware',
29 | 'django.middleware.common.CommonMiddleware',
30 | 'django.middleware.csrf.CsrfViewMiddleware',
31 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
32 | 'django.contrib.messages.middleware.MessageMiddleware',
33 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
34 | 'opencensus.ext.django.middleware.OpencensusMiddleware'
35 | ]
36 |
37 | OPENCENSUS = {
38 | 'TRACE': {
39 | 'SAMPLER': 'opencensus.trace.samplers.ProbabilitySampler(rate=1)',
40 | 'EXPORTER': 'opencensus.ext.azure.trace_exporter.AzureExporter(connection_string="' + appKey + '")'
41 | }
42 | }
43 |
44 | # Static files (CSS, JavaScript, Images)
45 | # https://docs.djangoproject.com/en/4.0/howto/static-files/
46 |
47 | STATIC_URL = 'static/'
48 | STATICFILES_DIRS = (str(BASE_DIR.joinpath('static')),)
49 | STATIC_ROOT = str(BASE_DIR.joinpath('staticfiles'))
50 |
51 | WHITENOISE_USE_FINDERS = True
52 | WHITENOISE_AUTOREFRESH = True
53 | WHITENOISE_MANIFEST_STRICT = False
54 |
55 | ROOT_URLCONF = 'azureproject.urls'
56 |
57 | TEMPLATES = [
58 | {
59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
60 | 'DIRS': [],
61 | 'APP_DIRS': True,
62 | 'OPTIONS': {
63 | 'context_processors': [
64 | 'django.template.context_processors.debug',
65 | 'django.template.context_processors.request',
66 | 'django.contrib.auth.context_processors.auth',
67 | 'django.contrib.messages.context_processors.messages',
68 | ],
69 | },
70 | },
71 | ]
72 |
73 | MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
74 |
75 | WSGI_APPLICATION = 'azureproject.wsgi.application'
76 |
77 | AUTH_PASSWORD_VALIDATORS = [
78 | {
79 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
80 | },
81 | {
82 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
83 | },
84 | {
85 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
86 | },
87 | {
88 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
89 | },
90 | ]
91 |
92 |
93 | # Internationalization
94 | # https://docs.djangoproject.com/en/4.0/topics/i18n/
95 |
96 | LANGUAGE_CODE = 'en-us'
97 | TIME_ZONE = 'UTC'
98 | USE_I18N = True
99 | USE_TZ = True
100 |
101 | # Default primary key field type
102 | # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
103 |
104 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
105 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/azureproject/urls.py:
--------------------------------------------------------------------------------
1 | """azureproject URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/4.0/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 | from django.contrib import admin
17 | from django.urls import include, path
18 |
19 | urlpatterns = [
20 | path('', include('restaurant_review.urls')),
21 | path('admin/', admin.site.urls),
22 | ]
23 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/azureproject/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for azureproject project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | # Check for the WEBSITE_HOSTNAME environment variable to see if we are running in Azure App Service
15 | # If so, then load the settings from production.py
16 | settings_module = 'azureproject.production' if 'WEBSITE_HOSTNAME' in os.environ else 'azureproject.settings'
17 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', settings_module)
18 |
19 | application = get_wsgi_application()
20 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 | from dotenv import load_dotenv
6 |
7 | def main():
8 | """Run administrative tasks."""
9 | # If WEBSITE_HOSTNAME is defined as an environment variable, then we're running on Azure App Service
10 |
11 | # Only for Local Development - Load environment variables from the .env file
12 | if not 'WEBSITE_HOSTNAME' in os.environ:
13 | print("Loading environment variables for .env file")
14 | load_dotenv('./.env')
15 |
16 | # When running on Azure App Service you should use the production settings.
17 | settings_module = "azureproject.production" if 'WEBSITE_HOSTNAME' in os.environ else 'azureproject.development'
18 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', settings_module)
19 |
20 | try:
21 | from django.core.management import execute_from_command_line
22 | except ImportError as exc:
23 | raise ImportError(
24 | "Couldn't import Django. Are you sure it's installed and "
25 | "available on your PYTHONPATH environment variable? Did you "
26 | "forget to activate a virtual environment?"
27 | ) from exc
28 | execute_from_command_line(sys.argv)
29 |
30 | if __name__ == '__main__':
31 | main()
32 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/requirements.txt:
--------------------------------------------------------------------------------
1 | django
2 | psycopg2-binary
3 | python-dotenv
4 | whitenoise
5 | django-sslserver
6 | azure-storage-blob
7 | azure-identity
8 | opencensus
9 | opencensus-ext-azure
10 | opencensus-ext-django
11 | opencensus-ext-postgresql
12 | psutil
13 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/django_sample/restaurant_review/__init__.py
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Restaurant, Review
3 |
4 | # Register your models here.
5 |
6 | admin.site.register(Restaurant)
7 | admin.site.register(Review)
8 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class RestaurantReviewConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'restaurant_review'
7 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.2 on 2022-02-08 04:34
2 |
3 | import django.core.validators
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='Restaurant',
18 | fields=[
19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('name', models.CharField(max_length=50)),
21 | ('street_address', models.CharField(max_length=50)),
22 | ('description', models.CharField(max_length=250)),
23 | ],
24 | ),
25 | migrations.CreateModel(
26 | name='Review',
27 | fields=[
28 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
29 | ('user_name', models.CharField(max_length=20)),
30 | ('rating', models.IntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)])),
31 | ('review_text', models.CharField(max_length=500)),
32 | ('review_date', models.DateTimeField(verbose_name='review date')),
33 | ('restaurant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='restaurant_review.restaurant')),
34 | ],
35 | ),
36 | ]
37 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/migrations/0002_alter_review_rating.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-04-18 14:51
2 |
3 | import django.core.validators
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('restaurant_review', '0001_initial'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='review',
16 | name='rating',
17 | field=models.IntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)]),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/migrations/0003_restaurant_image_name_review_image_name.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-04-18 15:16
2 |
3 | from django.db import migrations, models
4 | import uuid
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('restaurant_review', '0002_alter_review_rating'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='restaurant',
16 | name='image_name',
17 | field=models.UUIDField(default=uuid.UUID('24ba6836-5acb-4e64-961a-b994de8cfb01')),
18 | ),
19 | migrations.AddField(
20 | model_name='review',
21 | name='image_name',
22 | field=models.UUIDField(default=uuid.UUID('58977b57-8293-4a84-bccc-34ef7bb138a4')),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/migrations/0004_alter_restaurant_image_name_alter_review_image_name.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-04-18 16:29
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('restaurant_review', '0003_restaurant_image_name_review_image_name'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='restaurant',
15 | name='image_name',
16 | field=models.UUIDField(),
17 | ),
18 | migrations.AlterField(
19 | model_name='review',
20 | name='image_name',
21 | field=models.UUIDField(),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/migrations/0005_remove_restaurant_image_name.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-04-19 16:12
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('restaurant_review', '0004_alter_restaurant_image_name_alter_review_image_name'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='restaurant',
15 | name='image_name',
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/migrations/0006_alter_review_image_name.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-04-19 17:03
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('restaurant_review', '0005_remove_restaurant_image_name'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='review',
15 | name='image_name',
16 | field=models.CharField(max_length=100),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/migrations/0007_alter_review_image_name.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-04-20 11:11
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('restaurant_review', '0006_alter_review_image_name'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='review',
15 | name='image_name',
16 | field=models.CharField(max_length=100, null=True),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/django_sample/restaurant_review/migrations/__init__.py
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.core.validators import MaxValueValidator, MinValueValidator
3 |
4 | # Create your models here.
5 | class Restaurant(models.Model):
6 | name = models.CharField(max_length=50)
7 | street_address = models.CharField(max_length=50)
8 | description = models.CharField(max_length=250)
9 | def __str__(self):
10 | return self.name
11 |
12 | class Review(models.Model):
13 | restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
14 | user_name = models.CharField(max_length=20)
15 | rating=models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])
16 | review_text = models.CharField(max_length=500)
17 | review_date = models.DateTimeField('review date')
18 | image_name = models.CharField(max_length=100, null=True)
19 | def __str__(self):
20 | return self.restaurant.name + " (" + self.review_date.strftime("%x") +")"
21 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/templates/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello Azure - Python Django and PostgreSQL Tutorial
4 |
5 |
6 |
7 |
8 | There was a problem (500 error) with the sample app.
9 | Try the following troubleshooting tips if
running locally :
10 |
11 | Check that you ran a migration first with
12 | "python manage.py migrate".
13 |
14 | Check that an .env file exists and defines the environment variables DBHOST, DBNAME, DBUSER, STORAGE_CONTAINER_NAME, and STORAGE_ACCOUNT_NAME.
15 |
16 | Check that the database exists in your local PostgresSQL server.
17 |
18 |
19 |
20 | Try the following troubleshooting tips if running in
Azure App Service :
21 |
22 | Check that you ran a migration first with
23 | "python manage.py migrate".
24 |
25 | Check that the environment variables (DBHOST, DBNAME, DBUSER, STORAGE_CONTAINER_NAME, and STORAGE_ACCOUNT_NAME) are defined as
26 | configuration settings ) for the service.
27 |
28 | Check that the database referenced in the DBNAME environment exists in the Azure PostgresSQL service.
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/templates/restaurant_review/base.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 |
3 |
4 |
5 | {% block head %}
6 | Django web app with PostgreSQL in Azure - {% block title %}{% endblock %}
7 |
8 |
9 |
10 |
11 | {% endblock %}
12 |
13 |
14 |
15 |
37 |
38 |
39 |
40 | {% block content %}{% endblock %}
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/templates/restaurant_review/create_restaurant.html:
--------------------------------------------------------------------------------
1 | {% extends "restaurant_review/base.html" %}
2 | {% block title %}Restaurant Create{% endblock %}
3 | {% block content %}
4 | Add New Restaurant
5 | {% if messages %}
6 |
7 | {% for message in messages %}
8 | {{ message }}
9 | {% endfor %}
10 |
11 | {% endif %}
12 |
13 |
29 | {% endblock %}
30 |
31 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/templates/restaurant_review/details.html:
--------------------------------------------------------------------------------
1 | {% extends "restaurant_review/base.html" %}
2 | {% load restaurant_extras %}
3 | {% block title %}Restaurant Details{% endblock %}
4 | {% block content %}
5 | {{ restaurant.name }}
6 |
7 |
8 |
Street address:
9 |
{{ restaurant.street_address }}
10 |
11 |
12 |
Description:
13 |
{{ restaurant.description }}
14 |
15 |
16 |
Rating:
17 |
{% star_rating restaurant.avg_rating restaurant.review_count %}
18 |
19 |
20 | Reviews
21 |
22 |
23 |
24 | Add new review
25 |
26 | {% if messages %}
27 |
28 | {% for message in messages %}
29 | {{ message }}
30 | {% endfor %}
31 |
32 | {% endif %}
33 |
34 |
35 |
36 | {% if restaurant.review_set %}
37 |
38 |
39 |
40 | Date
41 | User
42 | Rating
43 | Review
44 | Photo
45 |
46 |
47 |
48 | {% for review in restaurant.review_set.all %}
49 |
50 | {{ review.review_date }}
51 | {{ review.user_name }}
52 | {{ review.rating }}
53 | {{ review.review_text }}
54 |
55 | {% if review.image_name %}
56 |
57 |
58 |
59 | {% endif %}
60 |
61 |
62 |
63 | {% endfor %}
64 |
65 |
66 | {% else %}
67 | No reviews of this restaurant yet.
68 | {% endif %}
69 |
70 |
71 |
127 | {% endblock %}
128 |
129 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/templates/restaurant_review/index.html:
--------------------------------------------------------------------------------
1 | {% extends "restaurant_review/base.html" %}
2 | {% load restaurant_extras %}
3 | {% block title %}Restaurant List{% endblock %}
4 | {% block content %}
5 | Restaurants
6 |
7 | {% if restaurants %}
8 |
9 |
10 |
11 | Name
12 | Rating
13 | Details
14 |
15 |
16 |
17 | {% for restaurant in restaurants %}
18 |
19 | {{ restaurant.name }}
20 | {% star_rating restaurant.avg_rating restaurant.review_count %}
21 | Details
22 |
23 | {% endfor %}
24 |
25 |
26 | {% else %}
27 | No restaurants exist. Select Add new restaurant to add one.
28 | {% endif %}
29 |
30 |
33 | {% endblock %}
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/templates/restaurant_review/star_rating.html:
--------------------------------------------------------------------------------
1 | {% if review_count %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {{ avg_rating|floatformat:1 }} ({{review_count}} {% if review_count == 1 %} review{% else %} reviews{% endif %})
21 |
22 |
23 | {% else %}
24 | No ratings yet
25 | {% endif %}
26 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/django_sample/restaurant_review/templatetags/__init__.py
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/templatetags/restaurant_extras.py:
--------------------------------------------------------------------------------
1 | from django import template
2 |
3 | register = template.Library()
4 |
5 |
6 | @register.inclusion_tag('restaurant_review/star_rating.html')
7 | def star_rating(avg_rating, review_count):
8 | stars_percent = round((avg_rating / 5.0) * 100) if review_count > 0 else 0
9 | return {'avg_rating': avg_rating, 'review_count': review_count, 'stars_percent': stars_percent}
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from . import views
3 |
4 | urlpatterns = [
5 | path('', views.index, name='index'),
6 | path('/', views.details, name='details'),
7 | path('create', views.create_restaurant, name='create_restaurant'),
8 | path('add', views.add_restaurant, name='add_restaurant'),
9 | path('review/', views.add_review, name='add_review'),
10 | ]
--------------------------------------------------------------------------------
/azure_monitor/django_sample/restaurant_review/views.py:
--------------------------------------------------------------------------------
1 | import uuid
2 | import os
3 | import azureproject.app_insights
4 |
5 | from django.http import Http404, HttpResponseRedirect
6 | from django.shortcuts import render
7 | from django.db.models import Avg, Count
8 | from django.urls import reverse
9 | from django.utils import timezone
10 | from django.contrib import messages
11 | from azure.identity import DefaultAzureCredential
12 | from azure.storage.blob import BlobServiceClient
13 | from requests import RequestException, exceptions
14 | from azureproject.get_token import get_token
15 | from azureproject.app_insights import *
16 |
17 | from restaurant_review.models import Restaurant, Review
18 |
19 | # Create your views here.
20 |
21 | def index(request):
22 | print('Request for index page received')
23 | get_token()
24 | restaurants = Restaurant.objects.annotate(avg_rating=Avg('review__rating')).annotate(review_count=Count('review'))
25 | return render(request, 'restaurant_review/index.html', {'restaurants': restaurants })
26 |
27 |
28 | def details(request, id):
29 | print('Request for restaurant details page received')
30 | get_token()
31 |
32 | # Get account_url based on environment
33 | account_url = get_account_url()
34 | image_path = account_url + "/" + os.environ['STORAGE_CONTAINER_NAME']
35 |
36 | try:
37 | restaurant = Restaurant.objects.annotate(avg_rating=Avg('review__rating')).annotate(review_count=Count('review')).get(pk=id)
38 | except Restaurant.DoesNotExist:
39 | raise Http404("Restaurant doesn't exist")
40 | return render(request, 'restaurant_review/details.html', {'restaurant': restaurant,
41 | 'image_path': image_path})
42 |
43 |
44 | def create_restaurant(request):
45 | print('Request for add restaurant page received')
46 |
47 | return render(request, 'restaurant_review/create_restaurant.html')
48 |
49 | def add_restaurant(request):
50 | get_token()
51 | try:
52 | name = request.POST['restaurant_name']
53 | street_address = request.POST['street_address']
54 | description = request.POST['description']
55 | if (name == "" or description == ""):
56 | raise RequestException()
57 | except (KeyError, exceptions.RequestException) as e:
58 | # Redisplay the restaurant entry form.
59 | messages.add_message(request, messages.INFO, 'Restaurant not added. Include at least a restaurant name and description.')
60 | return HttpResponseRedirect(reverse('create_restaurant'))
61 | else:
62 | restaurant = Restaurant()
63 | restaurant.name = name
64 | restaurant.street_address = street_address
65 | restaurant.description = description
66 | Restaurant.save(restaurant)
67 |
68 | # log a new metric for a review
69 | tmap = tag_map_module.TagMap()
70 | tmap.insert("name", name)
71 | record_metric_resturant(tmap)
72 |
73 | return HttpResponseRedirect(reverse('details', args=(restaurant.id,)))
74 |
75 | def add_review(request, id):
76 | get_token()
77 | try:
78 | restaurant = Restaurant.objects.annotate(avg_rating=Avg('review__rating')).annotate(review_count=Count('review')).get(pk=id)
79 | except Restaurant.DoesNotExist:
80 | raise Http404("Restaurant doesn't exist")
81 |
82 | try:
83 | user_name = request.POST['user_name']
84 | rating = request.POST['rating']
85 | review_text = request.POST['review_text']
86 | if (user_name == "" or rating == ""):
87 | raise RequestException()
88 | except (KeyError, exceptions.RequestException) as e:
89 | # Redisplay the details page
90 | messages.add_message(request, messages.INFO, 'Review not added. Include at least a name and rating for review.')
91 | return HttpResponseRedirect(reverse('details', args=(id,)))
92 | else:
93 |
94 | if 'reviewImage' in request.FILES:
95 | image_data = request.FILES['reviewImage']
96 | print("Original image name = " + image_data.name)
97 | print("File size = " + str(image_data.size))
98 |
99 | if (image_data.size > 2048000):
100 | messages.add_message(request, messages.INFO, 'Image too big, try again.')
101 | return HttpResponseRedirect(reverse('details', args=(id,)))
102 |
103 | # Get account_url based on environment
104 | account_url = get_account_url()
105 |
106 | # Create client
107 | azure_credential = DefaultAzureCredential(exclude_shared_token_cache_credential=True)
108 | blob_service_client = BlobServiceClient(
109 | account_url=account_url,
110 | credential=azure_credential)
111 |
112 | # Get file name to use in database
113 | image_name = str(uuid.uuid4()) + ".png"
114 |
115 | # Create blob client
116 | blob_client = blob_service_client.get_blob_client(container=os.environ['STORAGE_CONTAINER_NAME'], blob=image_name)
117 | print("\nUploading to Azure Storage as blob:\n\t" + image_name)
118 |
119 | # Upload file
120 | with image_data as data:
121 | blob_client.upload_blob(data)
122 | else:
123 | # No image for review
124 | image_name=None
125 |
126 | review = Review()
127 | review.restaurant = restaurant
128 | review.review_date = timezone.now()
129 | review.user_name = user_name
130 | review.rating = rating
131 | review.review_text = review_text
132 | review.image_name = image_name
133 | Review.save(review)
134 |
135 | # log a new metric for a review
136 | tmap = tag_map_module.TagMap()
137 | tmap.insert("resturantId", str(id))
138 | record_metric_review(tmap)
139 |
140 | return HttpResponseRedirect(reverse('details', args=(id,)))
141 |
142 | def get_account_url():
143 | # Create LOCAL_USE_AZURE_STORAGE environment variable to use Azure Storage locally.
144 | if 'WEBSITE_HOSTNAME' in os.environ or ("LOCAL_USE_AZURE_STORAGE" in os.environ):
145 | return "https://%s.blob.core.windows.net" % os.environ['STORAGE_ACCOUNT_NAME']
146 | else:
147 | return os.environ['STORAGE_ACCOUNT_NAME']
148 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/django_sample/static/favicon.ico
--------------------------------------------------------------------------------
/azure_monitor/django_sample/static/images/Example-reviews.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/django_sample/static/images/Example-reviews.png
--------------------------------------------------------------------------------
/azure_monitor/django_sample/static/images/azure-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/static/restaurant.css:
--------------------------------------------------------------------------------
1 | body {
2 | min-height: 75rem;
3 | padding-top: 4.5rem;
4 | }
5 | .score {
6 | display: block;
7 | font-size: 16px;
8 | position: relative;
9 | overflow: hidden;
10 | }
11 |
12 | .score-wrap {
13 | display: inline-block;
14 | position: relative;
15 | height: 19px;
16 | }
17 |
18 | .score .stars-active {
19 | color: #EEBD01;
20 | position: relative;
21 | z-index: 10;
22 | display: inline-block;
23 | overflow: hidden;
24 | white-space: nowrap;
25 | }
26 |
27 | .score .stars-inactive {
28 | color: grey;
29 | position: absolute;
30 | top: 0;
31 | left: 0;
32 | -webkit-text-stroke: initial;
33 | }
34 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/staticfiles/note.txt:
--------------------------------------------------------------------------------
1 | Here for https://github.com/evansd/whitenoise/issues/215.
--------------------------------------------------------------------------------
/azure_monitor/django_sample/web_project/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/django_sample/web_project/__init__.py
--------------------------------------------------------------------------------
/azure_monitor/django_sample/web_project/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for web_project project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'web_project.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/web_project/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for web_project project.
3 |
4 | Generated by 'django-admin startproject' using Django 4.0.4.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.0/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/4.0/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 |
15 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
16 | BASE_DIR = Path(__file__).resolve().parent.parent
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = 'django-insecure-385jn!oj!%2yz67zpz*h$sb+8x41^i#)zdo+qo$=pd28^4-5-r'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 | ]
41 |
42 | MIDDLEWARE = [
43 | 'django.middleware.security.SecurityMiddleware',
44 | 'django.contrib.sessions.middleware.SessionMiddleware',
45 | 'django.middleware.common.CommonMiddleware',
46 | 'django.middleware.csrf.CsrfViewMiddleware',
47 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
48 | 'django.contrib.messages.middleware.MessageMiddleware',
49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
50 | ]
51 |
52 | ROOT_URLCONF = 'web_project.urls'
53 |
54 | TEMPLATES = [
55 | {
56 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
57 | 'DIRS': [],
58 | 'APP_DIRS': True,
59 | 'OPTIONS': {
60 | 'context_processors': [
61 | 'django.template.context_processors.debug',
62 | 'django.template.context_processors.request',
63 | 'django.contrib.auth.context_processors.auth',
64 | 'django.contrib.messages.context_processors.messages',
65 | ],
66 | },
67 | },
68 | ]
69 |
70 | WSGI_APPLICATION = 'web_project.wsgi.application'
71 |
72 |
73 | # Database
74 | # https://docs.djangoproject.com/en/4.0/ref/settings/#databases
75 |
76 | DATABASES = {
77 | 'default': {
78 | 'ENGINE': 'django.db.backends.sqlite3',
79 | 'NAME': BASE_DIR / 'db.sqlite3',
80 | }
81 | }
82 |
83 |
84 | # Password validation
85 | # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
86 |
87 | AUTH_PASSWORD_VALIDATORS = [
88 | {
89 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
90 | },
91 | {
92 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
93 | },
94 | {
95 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
96 | },
97 | {
98 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
99 | },
100 | ]
101 |
102 |
103 | # Internationalization
104 | # https://docs.djangoproject.com/en/4.0/topics/i18n/
105 |
106 | LANGUAGE_CODE = 'en-us'
107 |
108 | TIME_ZONE = 'UTC'
109 |
110 | USE_I18N = True
111 |
112 | USE_TZ = True
113 |
114 |
115 | # Static files (CSS, JavaScript, Images)
116 | # https://docs.djangoproject.com/en/4.0/howto/static-files/
117 |
118 | STATIC_URL = 'static/'
119 |
120 | # Default primary key field type
121 | # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
122 |
123 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
124 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/web_project/urls.py:
--------------------------------------------------------------------------------
1 | """web_project URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/4.0/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 | from django.contrib import admin
17 | from django.urls import path
18 |
19 | urlpatterns = [
20 | path('admin/', admin.site.urls),
21 | ]
22 |
--------------------------------------------------------------------------------
/azure_monitor/django_sample/web_project/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for web_project project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'web_project.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/azure_monitor/env.template:
--------------------------------------------------------------------------------
1 | APPINSIGHTS_INSTRUMENTATIONKEY={INSIGHTS_KEY}
2 | APPLICATIONINSIGHTS_CONNECTION_STRING={INSIGHTS_CONNECTION_STRING}
3 |
4 | DB_SERVER=python-appinsights-{SUFFIX}.database.windows.net
5 | DB_DATABASE=python
6 | DB_USERNAME={DBUSER}
7 | DB_PASSWORD={DBPASSWORD}
8 | DB_PORT = 1433
9 |
10 | DBHOST=python-appinsights-{SUFFIX}-mysql.mysql.database.azure.com
11 | DBNAME=python
12 | DBUSER={DBUSER}@python-appinsights-{SUFFIX}-mysql
13 | DBPASS={DBPASSWORD}
14 |
15 | AZURE_STORAGE_CONNECTION_STRING={STORAGE_CONNECTION_STRING}
16 | FUNCTION_URL={FUNCTION_URL}
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | page_type: sample
3 | languages:
4 | - python
5 | - html
6 | products:
7 | - azure
8 | description: "This sample contains a simple Flask application to show how you can instrument the OpenCensus Azure Monitor exporters as well as track telemetry from popular Python libraries via OpenCensus integrations."
9 | urlFragment: azure-monitor-opencensus-python
10 | ---
11 |
12 | # Flask "To-Do" Sample Application
13 |
14 | ## Setup
15 |
16 | 1. This package is not hosted on Pypi. You can install all dependencies locally using `pip install -r requirements.txt`.
17 | 2. To send telemetry to Azure Monitor, pass in your instrumentation key into `INSTRUMENTATION_KEY` in `config.py`.
18 |
19 | ```
20 | INSTRUMENTATION_KEY =
21 | ```
22 |
23 | The default database URI links to a sqlite database `app.db`. To configure a different database, you can modify `config.py` and change the `SQLALCHEMY_DATABASE_URI` value to point to a database of your choosing.
24 |
25 | ```
26 | SQLALCHEMY_DATABASE_URI =
27 | ```
28 |
29 | ## Usage
30 |
31 | 1. Navigate to where `azure_monitor\flask_sample` is located.
32 | 2. Run the main application via `python sample.py`.
33 | 4. Hit your local endpoint (should be http://localhost:5000). This should open up a browser to the main page.
34 | 5. On the newly opened page, you can add tasks via the textbox under `Add a new todo item:`. You can enter any text you want (cannot be blank).
35 | 6. Click `Add Item` to add the task. The task will be added under `Incomplete Items`. Adding an item with greater than 10 characters will generate an error.
36 | 7. To utilize the `Save to File` feature, run the endpoint application via `python endpoint.py`. This will run another Flask application with a WSGI server running on http://localhost:5001. Click `Save to File` and all tasks will be written to a file `file.txt` in the `output` folder.
37 | 8. Each task has a `Mark As Complete` button. Clicking it will move the task from incomplete to completed.
38 | 9. You can also hit the `blacklist` url page to see a sample of a page that does not have telemetry being sent (http://localhost:5000/blacklist).
39 | 10. You can also run a command line interface application to hit the endpoints on you Flask application directly. Run `command.py` and follow the prompts accordingly.
40 |
41 | ## Types of telemetry sent
42 |
43 | There are various types of telemetry that are being sent in the sample application. Refer to [Telemetry Type in Azure Monitor](https://docs.microsoft.com/azure/azure-monitor/app/opencensus-python#telemetry-type-mappings). Every button click hits an endpoint that exists in the flask application, so they will be treated as incoming requests (`requests` table in Azure Monitor). A log message is also sent every time a button is clicked, so a log telemetry is sent (`traces` table in Azure Monitor). An exception telemetry is sent when an invalid task is entered (greater than 10 characters). A counter metric is recorded every time the `add` button is clicked. Metric telemetry is sent every interval (default 15.0 s, `customMetrics` table in Azure Monitor).
44 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/app.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/flask_sample/app.db
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/app/__init__.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import sys
3 |
4 | from flask import Flask
5 |
6 | sys.path.append('..')
7 | from config import Config
8 | from flask_sqlalchemy import SQLAlchemy
9 | from opencensus.ext.azure import metrics_exporter
10 | from opencensus.ext.azure.log_exporter import AzureLogHandler
11 | from opencensus.ext.flask.flask_middleware import FlaskMiddleware
12 | from opencensus.trace import config_integration
13 |
14 | logger = logging.getLogger(__name__)
15 | app = Flask(__name__)
16 | app.config.from_object(Config)
17 |
18 | db = SQLAlchemy(app)
19 |
20 | # Import here to avoid circular imports
21 | from app import routes # noqa isort:skip
22 |
23 | # Trace integrations for sqlalchemy library
24 | config_integration.trace_integrations(['sqlalchemy'])
25 |
26 | # Trace integrations for requests library
27 | config_integration.trace_integrations(['requests'])
28 |
29 | # FlaskMiddleware will track requests for the Flask application and send
30 | # request/dependency telemetry to Azure Monitor
31 | middleware = FlaskMiddleware(app)
32 |
33 | # Processor function for changing the role name of the app
34 | def callback_function(envelope):
35 | envelope.tags['ai.cloud.role'] = "To-Do App"
36 | return True
37 |
38 | # Adds the telemetry processor to the trace exporter
39 | middleware.exporter.add_telemetry_processor(callback_function)
40 |
41 | # Exporter for metrics, will send metrics data
42 | exporter = metrics_exporter.new_metrics_exporter(
43 | enable_standard_metrics=False,
44 | connection_string='InstrumentationKey=' + Config.INSTRUMENTATION_KEY)
45 |
46 | # Exporter for logs, will send logging data
47 | logger.addHandler(
48 | AzureLogHandler(
49 | connection_string='InstrumentationKey=' + Config.INSTRUMENTATION_KEY
50 | )
51 | )
52 |
53 |
54 | if __name__ == '__main__':
55 | app.run(host='localhost', port=5000, threaded=True, debug=True)
56 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/app/forms/__init__.py:
--------------------------------------------------------------------------------
1 | from flask_wtf import FlaskForm
2 | from wtforms import StringField, SubmitField
3 | from wtforms.validators import DataRequired
4 |
5 |
6 | class ToDoForm(FlaskForm):
7 | add_input = StringField('To Do', validators=[DataRequired()])
8 | mark_submit = SubmitField('Mark As Complete')
9 | valid_submit = SubmitField('Save to File')
10 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/app/metrics.py:
--------------------------------------------------------------------------------
1 | from opencensus.stats import aggregation as aggregation_module
2 | from opencensus.stats import measure as measure_module
3 | from opencensus.stats import stats as stats_module
4 | from opencensus.stats import view as view_module
5 | from opencensus.tags import tag_map as tag_map_module
6 |
7 | stats = stats_module.stats
8 | view_manager = stats.view_manager
9 | stats_recorder = stats.stats_recorder
10 |
11 | request_measure = measure_module.MeasureInt("added tasks",
12 | "number of added tasks",
13 | "tasks")
14 | request_view = view_module.View("added tasks",
15 | "number of tasks",
16 | ["application_type"],
17 | request_measure,
18 | aggregation_module.CountAggregation())
19 | view_manager.register_view(request_view)
20 | mmap = stats_recorder.new_measurement_map()
21 | tmap = tag_map_module.TagMap()
22 | tmap.insert("application_type", "flask")
23 | tmap.insert("os_type", "linux")
24 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/app/models/__init__.py:
--------------------------------------------------------------------------------
1 | from app import db
2 |
3 |
4 | class Todo(db.Model):
5 | id = db.Column(db.Integer, primary_key=True)
6 | text = db.Column(db.String(200))
7 | complete = db.Column(db.Boolean)
8 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/app/routes.py:
--------------------------------------------------------------------------------
1 | import json
2 | import requests
3 |
4 | from flask import flash, make_response, redirect, render_template, request, url_for
5 |
6 | from app import app, db, logger
7 | from app.forms import ToDoForm
8 | from app.metrics import mmap, request_measure, tmap
9 | from app.models import Todo
10 |
11 | # Hitting any endpoint will track an incoming request (requests)
12 |
13 |
14 | @app.route('/')
15 | @app.route('/error')
16 | def index():
17 | form = ToDoForm()
18 | # Queries to the data base will track an outgoing request (dependencies)
19 | incomplete = Todo.query.filter_by(complete=False).all()
20 | complete = Todo.query.filter_by(complete=True).all()
21 | path = request.url_rule
22 | if path and 'error' in path.rule:
23 | flash('ERROR: String must be less than 11 characters.')
24 | return render_template(
25 | 'index.html',
26 | title='Home',
27 | form=form,
28 | complete=complete,
29 | incomplete=incomplete
30 | )
31 |
32 |
33 | @app.route('/save', methods=['POST'])
34 | def save():
35 | incomplete = Todo.query.filter_by(complete=False).all()
36 | complete = Todo.query.filter_by(complete=True).all()
37 | incomplete.extend(complete)
38 | url = "http://localhost:5001/api/save"
39 | entries = ["Id: " + str(entry.id) + " Task: " + entry.text + " Complete: " + str(entry.complete) \
40 | for entry in incomplete]
41 | response = requests.post(url=url, data=json.dumps(entries))
42 | if response.ok:
43 | flash("Todo saved to file.")
44 | else:
45 | logger.error(response.reason)
46 | flash("Exception occurred while saving")
47 | return redirect('/')
48 |
49 |
50 | @app.route('/blacklist')
51 | def blacklist():
52 | return render_template('blacklist.html')
53 |
54 |
55 | @app.route('/add', methods=['POST'])
56 | def add():
57 | add_input = request.form['add_input']
58 | # Fail if string greater than 10 characters
59 | try:
60 | if len(add_input) > 10:
61 | raise Exception
62 | todo = Todo(text=add_input, complete=False)
63 | db.session.add(todo)
64 | db.session.commit()
65 | # Logging with the logger will be tracked as logging telemetry (traces)
66 | logger.warn("Added entry: " + todo.text)
67 | # Records a measure metric to be sent as telemetry (customMetrics)
68 | mmap.measure_int_put(request_measure, 1)
69 | mmap.record(tmap)
70 | except Exception:
71 | logger.exception("ERROR: Input length too long.")
72 | return redirect('/error')
73 | return redirect('/')
74 |
75 |
76 | @app.route('/complete/', methods=['POST'])
77 | def complete(id):
78 | todo = Todo.query.filter_by(id=int(id)).first()
79 | todo.complete = True
80 | db.session.commit()
81 | logger.warn("Marked complete: " + todo.text)
82 | return redirect('/')
83 |
84 | ### Endpoints for CLI demo ###
85 |
86 |
87 | @app.route('/get/incomplete')
88 | def get_incomplete():
89 | incomplete = Todo.query.filter_by(complete=False).all()
90 | return json.dumps([(task.id, task.text) for task in incomplete])
91 |
92 |
93 | @app.route('/get/complete')
94 | def get_complete():
95 | complete = Todo.query.filter_by(complete=True).all()
96 | return json.dumps([(task.id, task.text) for task in complete])
97 |
98 |
99 | @app.route('/add/', methods=['POST'])
100 | def add_task(task):
101 | try:
102 | print("Task Received: " + task)
103 | if len(task) > 10:
104 | raise Exception
105 | todo = Todo(text=task, complete=False)
106 | db.session.add(todo)
107 | db.session.commit()
108 | # Logging with the logger will be tracked as logging telemetry (traces)
109 | logger.warn("Added entry: " + todo.text)
110 | # Records a measure metric to be sent as telemetry (customMetrics)
111 | mmap.measure_int_put(request_measure, 1)
112 | mmap.record(tmap)
113 | except Exception:
114 | logger.exception("ERROR: Input length too long.")
115 | return make_response("ERROR: Input length too long.", 500)
116 | return make_response("Successfully added task.", 200)
117 |
118 |
119 | @app.route('/complete/task/', methods=['POST'])
120 | def complete_task(id):
121 | todo = Todo.query.filter_by(id=int(id)).first()
122 | todo.complete = True
123 | db.session.commit()
124 | logger.warn("Marked complete: " + todo.text)
125 | return make_response("Success", 200)
126 |
127 |
128 | @app.route('/save/tasks', methods=['POST'])
129 | def save_tasks():
130 | incomplete = Todo.query.filter_by(complete=False).all()
131 | complete = Todo.query.filter_by(complete=True).all()
132 | incomplete.extend(complete)
133 | url = "http://localhost:5001/api/save"
134 | entries = ["Id: " + str(entry.id) + " Task: " + entry.text + " Complete: " + str(entry.complete) \
135 | for entry in incomplete]
136 | response = requests.post(url=url, data=json.dumps(entries))
137 | if response.ok:
138 | logger.warn("Todo saved to file.")
139 | return make_response("Todo saved to file.", 200)
140 | else:
141 | logger.error(response.reason)
142 | return make_response("Exception occurred while saving.", 500)
143 | return redirect('/')
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/app/static/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | line-height: 1.4em;
4 | background: #f5f5f5;
5 | color: #111111;
6 | min-width: 230px;
7 | max-width: 550px;
8 | margin: 0 auto;
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 | font-weight: 300;
12 | }
13 |
14 | .todoapp {
15 | background: #fff;
16 | margin: 130px 0 40px 0;
17 | position: relative;
18 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
19 | 0 25px 50px 0 rgba(0, 0, 0, 0.1);
20 | }
21 |
22 | .header {
23 | background: #f5f5f5;
24 | position: relative;
25 | }
26 |
27 | .todoapp h1 {
28 | position: absolute;
29 | top: -140px;
30 | width: 100%;
31 | font-size: 80px;
32 | font-weight: 200;
33 | text-align: center;
34 | color: #b83f45;
35 | text-rendering: optimizeLegibility;
36 | }
37 |
38 | .header-container div {
39 | display: inline-block;
40 | height: 100%;
41 | }
42 |
43 | /* Save Button */
44 | .save-button {
45 | box-shadow:inset 0px 39px 0px -24px #e67a73;
46 | background-color:#e4685d;
47 | border-radius:4px;
48 | border:1px solid #ffffff;
49 | display:inline-block;
50 | cursor:pointer;
51 | color:#ffffff;
52 | font-family:Arial;
53 | font-size:15px;
54 | padding:6px 15px;
55 | text-decoration:none;
56 | text-shadow:0px 1px 0px #b23e35;
57 | }
58 | .save-button:hover {
59 | background-color:#eb675e;
60 | }
61 | .save-button:active {
62 | position:relative;
63 | top:1px;
64 | }
65 |
66 | /* Entry section */
67 |
68 | .new-todo,
69 | .edit {
70 | position: relative;
71 | margin: 0;
72 | width: 100%;
73 | font-size: 24px;
74 | font-family: inherit;
75 | font-weight: inherit;
76 | line-height: 1.4em;
77 | color: inherit;
78 | padding: 6px;
79 | border: 1px solid #999;
80 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
81 | box-sizing: border-box;
82 | -webkit-font-smoothing: antialiased;
83 | -moz-osx-font-smoothing: grayscale;
84 | }
85 |
86 | .new-todo {
87 | padding: 16px 16px 16px 60px;
88 | border: none;
89 | background: rgba(0, 0, 0, 0.003);
90 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
91 | }
92 |
93 | .main {
94 | position: relative;
95 | z-index: 2;
96 | border-top: 1px solid #e6e6e6;
97 | }
98 |
99 | .todo-list {
100 | margin: 0;
101 | padding: 0;
102 | list-style: none;
103 | }
104 |
105 | .todo-list li {
106 | position: relative;
107 | font-size: 24px;
108 | border-bottom: 1px solid #ededed;
109 | }
110 |
111 | .todo-list li:last-child {
112 | border-bottom: none;
113 | }
114 |
115 | .list-element {
116 | width: 100%;
117 | text-align: left;
118 | font-size: 20px;
119 | }
120 |
121 | .footer {
122 | padding: 10px 15px;
123 | height: 20px;
124 | text-align: center;
125 | font-size: 15px;
126 | border-top: 1px solid #e6e6e6;
127 | }
128 |
129 | .footer:before {
130 | content: '';
131 | position: absolute;
132 | right: 0;
133 | bottom: 0;
134 | left: 0;
135 | height: 50px;
136 | overflow: hidden;
137 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
138 | 0 8px 0 -3px #f6f6f6,
139 | 0 9px 1px -3px rgba(0, 0, 0, 0.2),
140 | 0 16px 0 -6px #f6f6f6,
141 | 0 17px 2px -6px rgba(0, 0, 0, 0.2);
142 | }
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/app/templates/blacklist.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Todo App
6 |
7 |
8 |
9 | The url for this page is blacklisted so no tracing telemetry should be sent when hitting this page.
10 | Todo Page
11 |
12 |
13 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/app/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Todo App
6 |
7 |
8 |
9 |
10 |
31 |
36 |
37 | Incomplete Items
38 |
39 | {% for todo in incomplete %}
40 |
41 |
42 |
46 |
47 | {% endfor %}
48 |
49 | Completed Items
50 |
51 | {% for todo in complete %}
52 |
53 | {{ todo.text }}
54 |
55 | {% endfor %}
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/command.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import json
3 | import requests
4 |
5 | from cmd import Cmd
6 |
7 | logger = logging.getLogger(__name__)
8 | class MyPrompt(Cmd):
9 | intro = "Python Demo To-Do Application. Type ? to list commands"
10 |
11 | def do_exit(self, input):
12 | '''exit the application.'''
13 | print("Bye")
14 | return True
15 |
16 | def do_show(self, input):
17 | incomplete = []
18 | complete = []
19 | try:
20 | incomplete = requests.get("http://localhost:5000/get/incomplete")
21 | complete = requests.get("http://localhost:5000/get/complete")
22 | inc_data = json.loads(incomplete.text)
23 | com_data = json.loads(complete.text)
24 | print("Incomplete\n")
25 | print("----------\n")
26 | for entry in inc_data:
27 | print("Id: " + str(entry[0]) + " Task: " + entry[1] + "\n")
28 | print("Complete\n")
29 | print("----------\n")
30 | for entry in com_data:
31 | print("Id: " + str(entry[0]) + " Task: " + entry[1] + "\n")
32 | except Exception as ex:
33 | logger.exception("Error occured.")
34 | pass
35 |
36 | def do_add(self, text):
37 | try:
38 | response = requests.post("http://localhost:5000/add/" + text)
39 | print(response.text)
40 | if response.ok:
41 | self.do_show(None)
42 | except Exception as ex:
43 | logger.exception("Error occured.")
44 | pass
45 |
46 | def do_complete(self, id):
47 | try:
48 | response = requests.post("http://localhost:5000/complete/task/" + str(id))
49 | print(response.text)
50 | if response.ok:
51 | self.do_show(None)
52 | except Exception as ex:
53 | logger.exception("Error occured.")
54 | pass
55 |
56 | def do_save(self, input):
57 | try:
58 | response = requests.post("http://localhost:5000/save/tasks")
59 | print(response.text)
60 | if response.ok:
61 | self.do_show(None)
62 | except Exception as ex:
63 | logger.exception("Error occured.")
64 | pass
65 |
66 | MyPrompt().cmdloop()
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | basedir = os.path.abspath(os.path.dirname(__file__))
4 |
5 |
6 | class Config(object):
7 | SECRET_KEY = os.environ.get('SECRET_KEY') or 'secret-key'
8 | SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
9 | 'sqlite:///' + os.path.join(basedir, 'app.db')
10 | SQLALCHEMY_TRACK_MODIFICATIONS = False
11 | INSTRUMENTATION_KEY = os.environ.get('APPINSIGHTS_INSTRUMENTATIONKEY') or \
12 | ''
13 | CONNECTION_STRING = 'InstrumentationKey=' + INSTRUMENTATION_KEY
14 | sampler = 'opencensus.trace.samplers.ProbabilitySampler(rate=1.0)'
15 | OPENCENSUS = {
16 | 'TRACE': {
17 | 'SAMPLER': sampler,
18 | 'EXPORTER': 'opencensus.ext.azure.trace_exporter.AzureExporter(connection_string="'
19 | + CONNECTION_STRING + '")',
20 | 'BLACKLIST_PATHS': ['blacklist'],
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/endpoint.py:
--------------------------------------------------------------------------------
1 | from endpoint import endpoint_app
2 |
3 | endpoint_app.run(host='localhost', port=5001, threaded=True, debug=True)
4 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/endpoint/__init__.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from flask import Flask
3 |
4 | sys.path.append('..')
5 | endpoint_app = Flask(__name__)
6 |
7 | # Import here to avoid circular imports
8 | from endpoint import endpoint_routes # noqa isort:skip
9 |
10 |
11 | if __name__ == '__main__':
12 | endpoint_app.run(host='localhost', port=5001, threaded=True, debug=True)
13 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/endpoint/endpoint_routes.py:
--------------------------------------------------------------------------------
1 | import json
2 | import logging
3 |
4 | from endpoint import endpoint_app
5 | from flask import make_response, request
6 |
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | @endpoint_app.route('/api/save', methods=['POST'])
11 | def save_tasks():
12 | try:
13 | data = json.loads(request.data)
14 | with open('./output/file.txt', 'w') as file:
15 | for item in data:
16 | file.write(item)
17 | file.write('\n')
18 | except Exception as ex:
19 | logger.exception("Exception occurred while saving: ")
20 | return make_response("Server exception: " + ex, 500)
21 | return request.data
22 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/output/file.txt:
--------------------------------------------------------------------------------
1 | Id: 1 Task: Test Complete: False
2 | Id: 2 Task: asd Complete: False
3 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/requirements.txt:
--------------------------------------------------------------------------------
1 | flask-sqlalchemy >= 2.4.1
2 | flask-wtf >= 0.14.3
3 | opencensus-ext-azure >= 1.0.2
4 | opencensus-ext-flask >= 0.7.3
5 | opencensus-ext-requests >= 0.7.3
6 | opencensus-ext-sqlalchemy >= 0.1.2
7 |
--------------------------------------------------------------------------------
/azure_monitor/flask_sample/sample.py:
--------------------------------------------------------------------------------
1 | from app import app
2 |
3 | app.run(host='localhost', port=5000, threaded=True, debug=True)
4 |
--------------------------------------------------------------------------------
/azure_monitor/media/python_azure_dependencies.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/media/python_azure_dependencies.png
--------------------------------------------------------------------------------
/azure_monitor/media/python_azure_resources.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/media/python_azure_resources.png
--------------------------------------------------------------------------------
/azure_monitor/media/python_custommetrics-carrots-logs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/media/python_custommetrics-carrots-logs.png
--------------------------------------------------------------------------------
/azure_monitor/media/python_custommetrics-carrots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/media/python_custommetrics-carrots.png
--------------------------------------------------------------------------------
/azure_monitor/media/python_custommetrics-web-reviews.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/media/python_custommetrics-web-reviews.png
--------------------------------------------------------------------------------
/azure_monitor/media/python_functionapp_deploy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/media/python_functionapp_deploy.png
--------------------------------------------------------------------------------
/azure_monitor/media/python_simple_app_trace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/media/python_simple_app_trace.png
--------------------------------------------------------------------------------
/azure_monitor/media/python_simple_exception_custom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/media/python_simple_exception_custom.png
--------------------------------------------------------------------------------
/azure_monitor/media/python_simple_trace_trace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/media/python_simple_trace_trace.png
--------------------------------------------------------------------------------
/azure_monitor/media/python_webapp_requests.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/media/python_webapp_requests.png
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | This document covers the python logger class using [OpenCensus-Python sdk][1] for [Azure Application Insights][3].
4 |
5 | ## Application Monitoring
6 |
7 | Application monitoring is one of the important pillars for any software development project. To support monitoring and observability, software projects should be able to perform below tasks:
8 |
9 | * Ability to generate logs and send them to analytics workspace.
10 | * Ability for monitor exceptions.
11 | * Ability to send custom metrics or events from the application.
12 |
13 | Currently application monitoring for python projects is integrated with [Azure Application Insights][3] using [opencensus-python sdk][1].
14 |
15 | ## Why there is a need for Python Logger Class?
16 |
17 | Currently [Azure Application Insights][3] supports logging, distributed tracing, metric collection using [opencensus-python sdk][1]. To integrate any python project with `opencensus sdk`, user has to write a lot of bootstrap code and this needs to be repeated for all the python projects in the application.
18 | By implementing Python Logger class, all the bootstrap logic can be encapsulated inside a single class which then can be reused by multiple projects.
19 |
20 | For example, here is the link to Azure documentation for [opencensus-python sdk][1] and to add a very basic logger and tracer to a flask application following code is required:
21 |
22 | ```python
23 |
24 | import logging
25 | from opencensus.ext.azure.log_exporter import AzureLogHandler
26 | from opencensus.trace.samplers import ProbabilitySampler
27 | from opencensus.ext.azure.trace_exporter import AzureExporter
28 | from opencensus.trace.tracer import Tracer
29 | from opencensus.trace import config_integration
30 | from opencensus.ext.flask.flask_middleware import FlaskMiddleware
31 |
32 | APP_NAME = "Flask_App"
33 | APP = Flask(APP_NAME)
34 |
35 | config_integration.trace_integrations(["requests"])
36 | config_integration.trace_integrations(["logging"])
37 |
38 | def callback_add_role_name(envelope):
39 | """ Callback function for opencensus """
40 | envelope.tags["ai.cloud.role"] = APP_NAME
41 | return True
42 |
43 | app_insights_cs = "InstrumentationKey=" + APP_INSIGHTS_KEY
44 | logger = logging.getLogger(__name__)
45 | handler = AzureLogHandler(connection_string=app_insights_cs)
46 | handler.add_telemetry_processor(callback_add_role_name)
47 | logger.setLevel(logging.INFO)
48 | logger.addHandler(handler)
49 |
50 | azure_exporter = AzureExporter(connection_string=app_insights_cs)
51 | azure_exporter.add_telemetry_processor(callback_add_role_name)
52 |
53 | FlaskMiddleware(
54 | APP, exporter=azure_exporter, sampler=ProbabilitySampler(rate=1.0),
55 | )
56 |
57 | tracer = Tracer(exporter=azure_exporter, sampler=ProbabilitySampler(1.0))
58 |
59 | ```
60 |
61 | This is the bare minimum code required for creating logger and tracer. However, to make it production ready, more complex logic is required.
62 | Following are some of the scenarios which are not covered in basic implementation.
63 |
64 | * Set logging config values from a json file.
65 | * Read application insights key from environment variable or configuration passed during initialization of loggers.
66 | * Add support for various integrations like flask, sql, httplib, django, pymysql etc
67 | * Adding default dimensions to the logger so that those dimensions get logged with every logging statement.
68 | * Adding [cloud role name][4]. This is required to give the name of component which is required to identify components in [Application map][4].
69 | * Add disabled logger which can be used during Unit testing.
70 |
71 | Assuming, if we need to add code for above scenarios, then this will get repeated for every python project in our application. To avoid repetition and hence enabling code reuse, this python logger class is required.
72 |
73 | ## Prerequisites for using AppLogger
74 |
75 | * An Azure subscription
76 | * Create an [Application Insights resource](https://docs.microsoft.com/azure/azure-monitor/app/create-new-resource) and store the application insights instrumentation key
77 | which is used in python logger class.
78 | * Using one of the supported [Python versions](https://github.com/census-instrumentation/opencensus-python/blob/master/setup.py#L25)
79 |
80 | ## Design
81 |
82 | ### AppLogger
83 |
84 | AppLogger is the logging class which contains bootstrap code to initialize logger using opencensus python sdk.
85 |
86 | ```python
87 | class AppLogger:
88 | def __init__(self, config=None):
89 | """Create an instance of the Logger class.
90 |
91 | Args:
92 | config:([dict], optional):
93 | Contains the setting for logger {"log_level": "DEBUG","logging_enabled":"true"",
94 | "app_insights_key":""}
95 | """
96 | ...
97 |
98 | def get_logger(self, component_name="AppLogger", custom_dimensions={}):
99 | ...
100 | def get_tracer(self, component_name="AppLogger", parent_tracer=None):
101 | ...
102 | def get_event_logger(self, component_name="AppLogger", custom_dimensions={}):
103 | ...
104 |
105 | ```
106 |
107 | Parameters used in initialization AppLogger:
108 |
109 | * **config**:(optional): This is an optional parameter. Its a python `dict` which takes config values required for logger.
110 |
111 | ```json
112 | config = {
113 | "log_level": "DEBUG",
114 | "logging_enabled": "true",
115 | "app_insights_key": "",
116 | }
117 | ```
118 |
119 | 1. **log_level**(optional): Log level can be set by passing desired value in config dict.
120 | Following log levels are supported
121 | * "INFO"
122 | * "DEBUG"
123 | * "WARN"
124 | * "ERROR"
125 | * "CRITICAL"
126 |
127 | > Default value for `log_level` is set to "INFO"
128 |
129 | 2. **logging_enabled**(optional): This is used to enable or disable the logging. By default its value is set to "true". To disable logging `logging_enabled` can be set to `false`. This is useful when we need to run unit tests and don't want to send telemetry. Following could be scenarios where logging can be turned off:
130 | 1. Unit Tests
131 | 1. Local development
132 |
133 | > Please make sure to set application insights key even when logging_enabled is set to "false" otherwise, it will throw exception during creation of logger.
134 |
135 | 3. **app_insights_key**: This contains the value of application insights key. If **not set** here, it should be set as an environment variable as `APPINSIGHTS_INSTRUMENTATION_KEY="YOUR KEY"`.
136 |
137 | > If application insights key is neither set in config, nor in environment variable, initialization of AppLogger will fail with exception.
138 |
139 | * **Function `get_logger`**
140 |
141 | ```python
142 | def get_logger(self, component_name="AppLogger", custom_dimensions={}):
143 | ```
144 |
145 | `get_logger` function adds AzureLogHandler to the logger and also adds `ai.cloud.role`= [component_name][6] to the logger. It also adds the default dimensions and returns a logger object.
146 |
147 | * **Function `get_tracer`**
148 |
149 | ```python
150 | def get_tracer(self, component_name="AppLogger", parent_tracer=None):
151 | ```
152 |
153 | `get_tracer` function return a Tracer object. It sets the [component_name][6] to `ai.cloud.role` which is used to identify component in application map. It also sets parent_tracer for proper correlation.
154 |
155 | * **Function `get_event_logger`**
156 |
157 | ```python
158 | def get_event_logger(self, component_name="AppLogger", custom_dimensions={}):
159 | ```
160 |
161 | `get_event_logger` function adds AzureEventHandler to the logger and also adds `ai.cloud.role`= [component_name][6] to the logger. It also adds the default dimensions and returns a logger object.
162 |
163 |
164 | * **Function `enable_flask`**
165 |
166 | ```python
167 | def enable_flask(self,flask_app,component_name="AppLogger"):
168 | ```
169 |
170 | `enable_flask` function enables Flask middleware as mentioned in the [documentation.][5] This is required to enable tracing for flask applications.
171 |
172 | Currently function for enabling flask is added but if application requires [integration][7] with other libraries like httplib, django, mysql, pymysql, pymongo, fastapi, postgresql etc then corresponding functions need to be added.
173 |
174 | * **Parameters used in `get_logger`, `get_tracer` and `get_event_logger`functions in AppLogger**:
175 |
176 | * **component_name**: (optional): Default value of this param is "AppLogger". Its always best practice to pass this parameter as the name of component in which logger is initialized eg "API3" etc. This will be help in filtering logs in application insights based on the `component_name`. This will appear as `cloud_RoleName` in app insight logs. Here is screenshot of application map in application insights:
177 | 
178 |
179 | * **parent_tracer**:
180 | This is required to set the correlation for tracing. Ideally this is required to set correlation of logs for modules which are called within same process.
181 | Lets takes an example where we have `main.py` which calls a function in `package1`. Now, to correlate logs generated from main.py and package1, a `tracer` is created in `main.py` which is passed to `package1_function` as `parent_tracer`. In `package1_function` a tracer is created where parent `traceid` is set in `get_tracer` function.
182 |
183 | By correlating traceids, all the logs can be filtered with one traceid.
184 |
185 | > Please note that in Azure Application Insights, `traceid` is referred as `operation_id`.
186 |
187 | ```py
188 | # main.py
189 | config={"app_insights_key"=""}
190 | app_logger= AppLogger(config)
191 | tracer= app_logger.get_tracer(component_name="main")
192 | # call package1 function
193 | some_result = package1_function(app_logger=app_logger,parent_tracer= tracer)
194 |
195 | # package1
196 | config={"app_insights_key"=""}
197 | def package1_function(app_logger, parent_tracer=None):
198 | app_logger = AppLogger(config)
199 | tracer = app_logger.get_tracer(component_name="package1",parent_tracer)
200 | ```
201 |
202 | Here is the screenshot of application insights showing correlation of traceids.
203 |
204 | 
205 |
206 | * **custom_dimensions**: Custom dimensions is a dict containing 'key:value' pairs for setting default custom dimension for logger.
207 |
208 | ```py
209 | custom_dimenions = {
210 | "key1":"value1",
211 | "key2":"value2"
212 | }
213 | app_logger= AppLogger(config)
214 | logger = app_logger.get_logger(self, component_name="Package1", custom_dimensions=custom_dimensions):
215 |
216 | def my_method(self, **kwargs):
217 | # Do something
218 | # Then log
219 | logger.warning("Some warning", extra=MyParam)
220 | logger.info("Some info",extra=MyParam)
221 | ```
222 |
223 | ### Create spans and dependency tracking
224 |
225 | To track and log time of any function, tracer span can be used as shown in following example:
226 |
227 | ```py
228 | config = {
229 | "log_level": "DEBUG",
230 | "logging_enabled": "true",
231 | "app_insights_key": "",
232 | }
233 | app_logger = AppLogger(config)
234 | logger = app_logger.get_logger(component_name="SomeModule")
235 | tracer = app_logger.get_tracer(component_name="SomeModule")
236 | with tracer.span("testspan"):
237 | test_function(app_logger)
238 |
239 | def test_function(app_logger=get_disabled_logger()):
240 | pass
241 | ```
242 |
243 | Above code generates entry in dependency table in application insights for `target` = `testspan` with time duration taken by `test_function`. For example, in below screenshot we can see that time taken by `util_func` is around 19 sec.
244 |
245 | 
246 |
247 | More info about dependency monitoring can be found [here.][7]
248 |
249 | ### Unit testing
250 |
251 | Unit tests for application using `AppLogger` can use either `logging_enabled` = `false` or `get_disabled_logger()`. This will disable logging during unit tests execution.
252 |
253 | Following example shows the usage of `logging_enabled` = `false` and `get_disabled_logger()` in two unit tests.
254 |
255 | ```python
256 |
257 | from SomeModule import my_method
258 | import uuid
259 | def test_my_method():
260 | config = {
261 | "log_level": "DEBUG",
262 | "logging_enabled": "false",
263 | "app_insights_key": str(uuid.uuid1()),
264 | }
265 | component_name = "TestComponent"
266 | app_logger = AppLogger(
267 | config=config
268 | )
269 | assert app_logger is not None
270 | logger = app_logger.get_logger(component_name=component_name)
271 | assert logger is not None
272 |
273 | def test_my_method():
274 | app_logger = get_disabled_logger()
275 | logger = app_logger.get_logger()
276 | assert logger is not None
277 |
278 | ```
279 |
280 | ### Usage
281 |
282 | Here are steps required to use AppLogger
283 |
284 | 1. Follow Prerequisites mentioned in [section.](#Prerequisites)
285 | 2. Install pip packages
286 |
287 | ```bash
288 | pip install -r .\monitoring\requirements.txt
289 | ```
290 |
291 | 3. Checkout [examples](./monitoring/examples/README.md) to see the usage of AppLogger
292 |
293 | 4. To execute unit test, use below command:
294 |
295 | ```bash
296 | python -m unittest discover .\monitoring\tests\
297 | ```
298 |
299 | ### Examples
300 |
301 | Examples created using AppLogger can be found [here](./monitoring/examples/README.md)
302 |
303 |
304 | [1]: https://docs.microsoft.com/azure/azure-monitor/app/opencensus-python
305 | [2]: https://opencensus.io/
306 | [3]: https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview
307 | [4]: https://docs.microsoft.com/azure/azure-monitor/app/app-map?tabs=net#understanding-cloud-role-name-within-the-context-of-the-application-map
308 | [5]: https://github.com/census-instrumentation/opencensus-python/blob/master/contrib/opencensus-ext-flask/opencensus/ext/flask/flask_middleware.py
309 | [6]: https://docs.microsoft.com/azure/azure-monitor/app/correlation#role-names
310 | [7]: https://docs.microsoft.com/azure/azure-monitor/app/opencensus-python-dependency
311 |
312 | ### Contributors
313 |
314 | [Julien Chomarat](https://github.com/jchomarat)
315 | [Benjamin Guinebertière](https://github.com/benjguin)
316 | [Ankit Sinha](https://github.com/ankitbko)
317 | [Prabal Deb](https://github.com/prabdeb)
318 | [Megha Patil](https://github.com/meghapatilcode)
319 | [Srikantan Sankaran](https://github.com/ssrikantan)
320 | [Frédéric Le Coquil](https://github.com/flecoqui)
321 | [Anand Chugh](https://github.com/anandchugh)
322 |
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/examples/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | This document will give the overview about examples created using AppLogger class.
4 |
5 | ## Examples
6 |
7 | 1. **api_1.py**: This example contains the code showing the usage of disabled logger.
8 | 1. **api_2.py**: This is a flask api which exposes one rest end point and uses AppLogger
9 | 1. **api_3.py**: This is a flask api which exposes one rest end point and uses AppLogger. This rest end point calls:
10 | 1. Rest end point exposed in **api_2.py**
11 | 1. It also calls task1, which is an internal function.
12 | 1. It also calls task2, which is also an internal function.
13 | 1. **client**: This is client code which calls:
14 | 1. Rest end point exposed in **api_3.py**
15 | 1. Also calls `util_func` function in **util**
16 | It passes the tracer created in `client.py` to util_func as a parent_tracer.
17 | 1. **api_4.py**: This example contains the code showing the usage of event logger.
18 |
19 | ## Usage
20 |
21 | Following are the steps to execute the examples:
22 |
23 | 1. Follow Prerequisites mentioned in [section.](../../README.md#prerequisites-for-using-applogger)
24 |
25 | 1. Get Application Insights Instrumentation Key (app_insights_instrumentation_key) and update it in **logging_config.json**
26 |
27 | ```json
28 | {
29 | "logging_enabled":"true",
30 | "log_level":"INFO",
31 | "app_insights_key":""
32 | }
33 | ```
34 |
35 | 3. Install pip packages
36 |
37 | ```bash
38 | pip install -r .\monitoring\requirements.txt
39 | ```
40 |
41 | 4. Execute examples in following sequence:
42 |
43 | 1. Run api_2.py flask app
44 |
45 | ```bash
46 | python .\monitoring\examples\api_2.py
47 | ```
48 |
49 | 2. Run api_3.py flask app
50 |
51 | ```bash
52 | python .\monitoring\examples\api_3.py
53 | ```
54 |
55 | 3. Run client.py
56 |
57 | ```bash
58 | python .\monitoring\examples\client.py
59 | ```
60 | 4. Run api_4.py flask app
61 |
62 | ```bash
63 | python .\monitoring\examples\api_4.py
64 | ```
65 |
66 | ## Results of executing examples:
67 |
68 | 1. Execution of `client.py` produces some logs on console
69 | ```sh
70 | ❯ python .\monitoring\examples\client.py
71 | 2021-04-13 14:58:26,399 name=client level=INFO traceId=3ac9716f42b11eb4c9d895b1ce528519 spanId=0000000000000000 Calling api 3
72 | response = b'{\n "data": "Success API3"\n}\n'
73 | 2021-04-13 14:58:31,300 name=util level=INFO traceId=3ac9716f42b11eb4c9d895b1ce528519 spanId=d0c25ebf22957d7e In util_func
74 | cess API3"\n}\n
75 | ```
76 |
77 | > Note the traceId mentioned in above logs `3ac9716f42b11eb4c9d895b1ce528519`
78 |
79 | This `traceid` can be used to filter logs in application insights.
80 |
81 | 2. Use following Kusto query to filter logs in application insights using traceId got in previous step.
82 |
83 | ```py
84 | traces
85 | | where operation_Id == ''
86 |
87 | ```
88 |
89 | 3. Use following Kusto query to filter dependency tracing using traceId got in the first step.
90 |
91 | ```py
92 | dependencies
93 | | where operation_Id == ''
94 |
95 | ```
96 |
97 | 4. Use following Kusto queries to filter custom events logged.
98 |
99 | To find number of times API4 is triggered:
100 |
101 | ```kusto
102 | customEvents
103 | | where name == 'Start_API4'
104 |
105 | ```
106 | To find execution time for API4:
107 |
108 | ```kusto
109 | customEvents
110 | | where name == 'API4_Execution_Time'
111 | | project name, customDimensions.execution_time
112 |
113 | ```
114 | To find the data inside JSON strings returned:
115 |
116 | ```kusto
117 | customEvents
118 | | where name == 'API4_Return_Json'
119 | | extend parsed_json = parse_json(tostring(customDimensions.response))
120 | | project name, parsed_json["data"]
121 |
122 | ```
123 |
124 | 5. Verify application map in application insights.
125 | 
126 |
127 | ## Appendix:
128 |
129 | The following table presents various variations of event logging and their corresponding definitions and use cases. Event logging is a crucial tool for collecting and analyzing data in order to make informed decisions and improve business outcomes. By understanding the different types of event logging variations and when to use them, organizations can better capture and analyze important data points to drive their business forward.
130 |
131 | | Event logging variation | What it is | Example use cases |
132 | | ------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------|
133 | | Plain Event | Event logged without any custom_dimension | Success & failure counts, start & end events |
134 | | Event with a simple custom dimension | Event with custom_dimension with a simple type like number or string | Execution time, number of tokens in a completion, number of completions in case of LLM calls |
135 | | Event with a complex custom dimension | Event with custom_dimension with a complex type like a json object, dictionary or list etc | To log JSON objects like REST API responses, evaluation metrics in JSON format in case of LLM calls |
136 |
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/examples/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/python_logger_opencensus_azure/monitoring/examples/__init__.py
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/examples/api_1.py:
--------------------------------------------------------------------------------
1 | """REST API Module using AppLogger"""
2 |
3 | import json
4 | from flask import Flask, jsonify
5 | import sys
6 | import os
7 | sys.path.append(os.path.join(os.getcwd(),'monitoring'))
8 |
9 | from src.logger import AppLogger, get_disabled_logger
10 | component_name ="API_1"
11 |
12 | logger = get_disabled_logger().get_logger(component_name=component_name)
13 |
14 | app = Flask(component_name)
15 |
16 | @app.route('/', methods=['GET'])
17 | def home():
18 | """End point for API1
19 |
20 | Returns:
21 | [Json]: [{'data': ''}]
22 | """
23 | logger.info("In API1 home function")
24 | return jsonify({'data': 'Success API1'})
25 |
26 | app.run(host="localhost", port=8000, debug=True)
27 |
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/examples/api_2.py:
--------------------------------------------------------------------------------
1 | """REST API Module using AppLogger"""
2 |
3 | import json
4 | import logging
5 | from flask import Flask, jsonify
6 | import sys
7 | import os
8 | sys.path.append(os.path.join(os.getcwd(),'monitoring'))
9 |
10 | from src.logger import AppLogger
11 | component_name ="API_2"
12 | app = Flask(component_name)
13 |
14 | logging_config_file_path = os.path.join(os.getcwd(),'monitoring',"examples","logging_config.json")
15 | with open(logging_config_file_path) as logging_config_file:
16 | logging_config = json.load(logging_config_file)
17 | app_logger = AppLogger(config=logging_config)
18 |
19 | app_logger.enable_flask(flask_app=app,component_name= component_name)
20 | logger = app_logger.get_logger(component_name=component_name)
21 | tracer = app_logger.get_tracer(component_name=component_name)
22 |
23 | @app.route('/', methods=['GET'])
24 | def home():
25 | """End point for API2
26 |
27 | Returns:
28 | [Json]: [{'data': ''}]
29 | """
30 | logger.info("In API2 home function")
31 | return jsonify({'data': 'Success API2'})
32 |
33 | app.run(host="localhost", port=8100, debug=True)
34 |
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/examples/api_3.py:
--------------------------------------------------------------------------------
1 | """REST API Module using AppLogger"""
2 | import logging
3 | import json
4 | from flask import Flask, jsonify
5 | import requests
6 | import sys
7 | import os
8 | sys.path.append(os.path.join(os.getcwd(),'monitoring'))
9 |
10 | from src.logger import AppLogger
11 | component_name ="API_3"
12 | app = Flask(component_name)
13 |
14 | logging_config_file_path = os.path.join(os.getcwd(),'monitoring',"examples","logging_config.json")
15 | with open(logging_config_file_path) as logging_config_file:
16 | logging_config = json.load(logging_config_file)
17 | app_logger = AppLogger(config=logging_config)
18 |
19 | app_logger.enable_flask(flask_app=app,component_name= component_name)
20 | logger = app_logger.get_logger(component_name=component_name)
21 | tracer = app_logger.get_tracer(component_name=component_name)
22 |
23 | @app.route('/', methods=['GET'])
24 | def home():
25 | """End point for API3
26 |
27 | Returns:
28 | [Json]: [{'data': ''}]
29 | """
30 | logger.info("In API3 home function")
31 |
32 | with tracer.span("API3_Task1"):
33 | ret_val_1 = task1()
34 |
35 | with tracer.span("API3_Task2"):
36 | ret_val_2 = task2()
37 |
38 | logger.info("Calling API 2")
39 | response = requests.get(url='http://localhost:8100/')
40 | print(f"response = {response.content}")
41 |
42 | return jsonify({'data': 'Success API3'})
43 |
44 | def task1():
45 | """Task1 function of API3
46 |
47 | Returns:
48 | [str]: [Return string]
49 | """
50 | logger.info("In API3 task1 function")
51 | return "task1 success!"
52 |
53 |
54 | def task2():
55 | """Task1 function of API2
56 |
57 | Returns:
58 | [str]: [Return string]
59 | """
60 | logger.info("In API3 task2 function")
61 | return "task2 success!"
62 |
63 | if __name__ == "__main__":
64 | app.run(host="localhost", port=8300,debug=True)
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/examples/api_4.py:
--------------------------------------------------------------------------------
1 | """REST API Module using AppLogger"""
2 |
3 | import json
4 | import logging
5 | from flask import Flask, jsonify
6 | import sys
7 | import os
8 | import time
9 | sys.path.append(os.path.join(os.getcwd(),'monitoring'))
10 |
11 | from src.logger import AppLogger
12 | component_name ="API_4"
13 | app = Flask(component_name)
14 |
15 | logging_config_file_path = os.path.join(os.getcwd(),'monitoring',"examples","logging_config.json")
16 | with open(logging_config_file_path) as logging_config_file:
17 | logging_config = json.load(logging_config_file)
18 | app_logger = AppLogger(config=logging_config)
19 |
20 | app_logger.enable_flask(flask_app=app,component_name= component_name)
21 | event_logger = app_logger.get_event_logger(component_name=component_name)
22 |
23 | @app.route('/', methods=['GET'])
24 | def home():
25 | """End point for API4
26 |
27 | Returns:
28 | [Json]: [{'data': ''}]
29 | """
30 | #Plain Event
31 | event_logger.info("Start_API4")
32 |
33 | start_time = time.time()
34 | jsonified_data= jsonify({'data': 'Success API4'})
35 |
36 | execution_time=time.time() - start_time
37 | extra_params = {"custom_dimensions": {"execution_time":execution_time}}
38 |
39 | #Event with an additional custom dimension execution_time
40 | event_logger.info("API4_Execution_Time", extra=extra_params)
41 |
42 | extra_params = {"custom_dimensions": {"response":jsonified_data}}
43 |
44 | #Event with an additional custom dimension with json value
45 | event_logger.info("API4_Return_Json", extra=extra_params)
46 |
47 | return jsonified_data
48 |
49 | app.run(host="localhost", port=8100, debug=True)
50 |
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/examples/client.py:
--------------------------------------------------------------------------------
1 | """"REST API Client Module using AppLogger"""
2 | import requests
3 | import logging
4 | import sys
5 | import os
6 | import pathlib
7 | import json
8 | sys.path.append(os.path.join(os.getcwd(),'monitoring'))
9 |
10 | from src.logger import AppLogger
11 | from util import util_func
12 |
13 | component_name = "client"
14 |
15 | logging_config_file_path = os.path.join(os.getcwd(),'monitoring',"examples","logging_config.json")
16 | with open(logging_config_file_path) as logging_config_file:
17 | logging_config = json.load(logging_config_file)
18 |
19 | app_logger = AppLogger(config=logging_config)
20 | logger = app_logger.get_logger(component_name=component_name)
21 | tracer = app_logger.get_tracer(component_name=component_name)
22 |
23 | def call_api_3():
24 | """Function calling endpoint of API3
25 | """
26 | logger.info("Calling api 3")
27 | response = requests.get(url='http://localhost:8300/')
28 | print(f"response = {response.content}")
29 |
30 | with tracer.span("util_func"):
31 | util_func(app_logger=app_logger,parent_tracer=tracer)
32 |
33 | if __name__ == '__main__':
34 | call_api_3()
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/examples/logging_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "logging_enabled":"true",
3 | "log_level":"INFO",
4 | "app_insights_key":""
5 | }
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/examples/util.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import sys
3 | import os
4 | sys.path.append(os.path.join(os.getcwd(),'monitoring'))
5 |
6 | from src.logger import AppLogger, get_disabled_logger
7 | from opencensus.trace.tracer import Tracer
8 |
9 | component_name="util"
10 |
11 | def util_func(app_logger=get_disabled_logger(), parent_tracer=None):
12 | """Util function
13 |
14 | Args:
15 | app_logger (AppLogger, optional): AppLogger Object. Defaults to get_disabled_logger().
16 | parent_tracer (Tracer, optional): Parent tracer. Defaults to None.
17 | """
18 | logger = app_logger.get_logger(component_name=component_name)
19 | tracer = app_logger.get_tracer(parent_tracer=parent_tracer )
20 | logger.info("In util_func")
21 |
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/img/application_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/python_logger_opencensus_azure/monitoring/img/application_map.png
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/img/dependency.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/python_logger_opencensus_azure/monitoring/img/dependency.png
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/img/operation_id.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/python_logger_opencensus_azure/monitoring/img/operation_id.png
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/requirements.txt:
--------------------------------------------------------------------------------
1 | opencensus-ext-azure
2 | opencensus-ext-logging
3 | opencensus-ext-flask
4 | opencensus-ext-requests
5 |
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/python_logger_opencensus_azure/monitoring/src/__init__.py
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/src/logger.py:
--------------------------------------------------------------------------------
1 | """This module is used to log traces into Azure Application Insights."""
2 | import functools
3 | import logging
4 | import uuid
5 | from os import getenv
6 |
7 | from opencensus.ext.azure.common import utils
8 | from opencensus.ext.azure.log_exporter import AzureLogHandler, AzureEventHandler
9 | from opencensus.ext.azure.trace_exporter import AzureExporter
10 | from opencensus.trace import config_integration
11 | from opencensus.trace.samplers import AlwaysOffSampler, AlwaysOnSampler
12 | from opencensus.trace.tracer import Tracer
13 | from opencensus.ext.flask.flask_middleware import FlaskMiddleware
14 | from opencensus.trace.samplers import ProbabilitySampler
15 |
16 |
17 | class CustomDimensionsFilter(logging.Filter):
18 | """Add custom-dimensions in each log by using filters."""
19 |
20 | def __init__(self, custom_dimensions=None):
21 | """Initialize CustomDimensionsFilter."""
22 | self.custom_dimensions = custom_dimensions or {}
23 |
24 | def filter(self, record):
25 | """Add the default custom_dimensions into the current log record."""
26 | dim = {**self.custom_dimensions, **getattr(record, "custom_dimensions", {})}
27 | record.custom_dimensions = dim
28 | return True
29 |
30 |
31 | class AppLogger:
32 | """Logger wrapper that attach the handler to Application Insights."""
33 |
34 | HANDLER_NAME = "Azure Application Insights Handler"
35 |
36 | EVENT_HANDLER_NAME = "Azure Application Insights Event Handler"
37 |
38 | def __init__(self, config=None):
39 | """Create an instance of the Logger class.
40 |
41 | Args:
42 | config:([dict], optional):
43 | Contains the setting for logger {"log_level": "DEBUG","logging_enabled":"true"",
44 | "app_insights_key":""}
45 | """
46 | config_integration.trace_integrations(["logging"])
47 | config_integration.trace_integrations(['requests'])
48 | self.config = {"log_level": logging.INFO, "logging_enabled": "true"}
49 | self.APPINSIGHTS_INSTRUMENTATION_KEY = "APPINSIGHTS_INSTRUMENTATION_KEY"
50 | self.update_config(config)
51 |
52 | def _initialize_azure_log_handler(self, component_name, custom_dimensions):
53 | """Initialize azure log handler."""
54 | # Adding logging to trace_integrations
55 | # This will help in adding trace and span ids to logs
56 | # https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-logging
57 |
58 | logging.basicConfig(
59 | format="%(asctime)s name=%(name)s level=%(levelname)s "
60 | "traceId=%(traceId)s spanId=%(spanId)s %(message)s"
61 | )
62 | app_insights_cs = "InstrumentationKey=" + self._get_app_insights_key()
63 | log_handler = AzureLogHandler(
64 | connection_string=app_insights_cs, export_interval=5.0
65 | )
66 | log_handler.add_telemetry_processor(self._get_callback(component_name))
67 | log_handler.name = self.HANDLER_NAME
68 | log_handler.addFilter(CustomDimensionsFilter(custom_dimensions))
69 | return log_handler
70 |
71 | def _initialize_azure_event_handler(self, component_name, custom_dimensions):
72 | """Initialize azure event handler."""
73 | app_insights_cs = "InstrumentationKey=" + self._get_app_insights_key()
74 | event_handler = AzureEventHandler(
75 | connection_string=app_insights_cs, export_interval=5.0
76 | )
77 | event_handler.add_telemetry_processor(self._get_callback(component_name))
78 | event_handler.name = self.EVENT_HANDLER_NAME
79 | event_handler.addFilter(CustomDimensionsFilter(custom_dimensions))
80 | return event_handler
81 |
82 | def _get_trace_exporter(self, component_name="AppLogger"):
83 | """[Get log exporter]
84 |
85 | Returns:
86 | [AzureExporter]: [Azure Trace Exporter]
87 | """
88 | app_insights_cs = "InstrumentationKey=" + self._get_app_insights_key()
89 | log_exporter = AzureExporter(
90 | connection_string=app_insights_cs, export_interval=0.0
91 | )
92 | log_exporter.add_telemetry_processor(self._get_callback(component_name))
93 | return log_exporter
94 |
95 | def _initialize_logger(self, log_handler, component_name):
96 | """Initialize Logger."""
97 | logger = logging.getLogger(component_name)
98 | logger.setLevel(self.log_level)
99 | if self.config.get("logging_enabled") == "true":
100 | if not any(x for x in logger.handlers if x.name == self.HANDLER_NAME or x.name == self.EVENT_HANDLER_NAME):
101 | logger.addHandler(log_handler)
102 | return logger
103 |
104 | def get_logger(self, component_name="AppLogger", custom_dimensions={}):
105 | """Get Logger Object.
106 |
107 | Args:
108 | component_name (str, optional): Name of logger. Defaults to "AppLogger".
109 | custom_dimensions (dict, optional): {"key":"value"} to capture with every log.
110 | Defaults to {}.
111 |
112 | Returns:
113 | Logger: A logger.
114 | """
115 | self.update_config(self.config)
116 | handler = self._initialize_azure_log_handler(component_name, custom_dimensions)
117 | return self._initialize_logger(handler, component_name)
118 |
119 | def get_event_logger(self, component_name="AppLogger", custom_dimensions={}):
120 | """Get Event ogger Object.
121 |
122 | Args:
123 | component_name (str, optional): Name of logger. Defaults to "AppLogger".
124 | custom_dimensions (dict, optional): {"key":"value"} to capture with every log.
125 | Defaults to {}.
126 |
127 | Returns:
128 | Logger: A logger.
129 | """
130 | self.update_config(self.config)
131 | handler = self._initialize_azure_event_handler(component_name, custom_dimensions)
132 | return self._initialize_logger(handler, component_name)
133 |
134 | def get_tracer(self, component_name="AppLogger", parent_tracer=None):
135 | """Get Tracer Object.
136 |
137 | Args:
138 | component_name (str, optional): Name of logger. Defaults to "AppLogger".
139 | parent_tracer([opencensus.trace.tracer], optional):
140 | Contains parent tracer required for setting coorelation.
141 |
142 | Returns:
143 | opencensus.trace.tracer: A Tracer.
144 | """
145 | self.update_config(self.config)
146 | sampler = AlwaysOnSampler()
147 | exporter = self._get_trace_exporter(component_name)
148 | if self.config.get("logging_enabled") != "true":
149 | sampler = AlwaysOffSampler()
150 | if parent_tracer is None:
151 | tracer = Tracer(exporter=exporter, sampler=sampler)
152 | else:
153 | tracer = Tracer(
154 | span_context=parent_tracer.span_context,
155 | exporter=exporter,
156 | sampler=sampler,
157 | )
158 | return tracer
159 |
160 | def enable_flask(self,flask_app,component_name="AppLogger"):
161 | """Enable flask for tracing
162 | For more info : https://github.com/census-instrumentation/opencensus-python/blob/master/contrib/opencensus-ext-flask/opencensus/ext/flask/flask_middleware.py
163 |
164 | Args:
165 | flask_app ([type]): [description]
166 | component_name (str, optional): [description]. Defaults to "AppLogger".
167 | """
168 | FlaskMiddleware(
169 | flask_app, exporter=self._get_trace_exporter(component_name=component_name)
170 | )
171 |
172 | def _get_app_insights_key(self):
173 | """Get Application Insights Key."""
174 | try:
175 | if self.app_insights_key is None:
176 | self.app_insights_key = getenv(
177 | self.APPINSIGHTS_INSTRUMENTATION_KEY, None
178 | )
179 | if self.app_insights_key is not None:
180 | utils.validate_instrumentation_key(self.app_insights_key)
181 | return self.app_insights_key
182 | else:
183 | raise Exception("ApplicationInsights Key is not set")
184 | except Exception as exp:
185 | raise Exception(f"Exception is getting app insights key-> {exp}")
186 |
187 | def _get_callback(self, component_name):
188 | """Adding cloud role name. This is required to give the name of component in application map.
189 | https://docs.microsoft.com/azure/azure-monitor/app/app-map?tabs=net#understanding-cloud-role-name-within-the-context-of-the-application-map
190 |
191 | Args:
192 | component_name ([str]): [The name of the component or applicaiton]
193 | """
194 | def _callback_add_role_name(envelope):
195 | """Add role name for logger."""
196 | envelope.tags["ai.cloud.role"] = component_name
197 | envelope.tags["ai.cloud.roleInstance"] = component_name
198 |
199 | return _callback_add_role_name
200 |
201 | def update_config(self, config=None):
202 | """Update logger configuration."""
203 | if config is not None:
204 | self.config.update(config)
205 | self.app_insights_key = self.config.get("app_insights_key")
206 | self.log_level = self.config.get("log_level")
207 |
208 |
209 | def get_disabled_logger():
210 | """Get a disabled AppLogger.
211 |
212 | Returns:
213 | AppLogger: A disabled AppLogger
214 | """
215 | return AppLogger(
216 | config={"logging_enabled": "false", "app_insights_key": str(uuid.uuid1())}
217 | )
218 |
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/azure-monitor-opencensus-python/32a22d93b315ec04e68a1bb5d3464f029fbf9649/azure_monitor/python_logger_opencensus_azure/monitoring/tests/__init__.py
--------------------------------------------------------------------------------
/azure_monitor/python_logger_opencensus_azure/monitoring/tests/test_logger.py:
--------------------------------------------------------------------------------
1 | """Test src/app_logger.py."""
2 |
3 | import logging
4 | import uuid
5 |
6 | import unittest
7 | from monitoring.src.logger import AppLogger, get_disabled_logger
8 |
9 | test_instrumentation_key = str(uuid.uuid1())
10 | test_invalid_instrumentation_key = "invalid_instrumentation_key"
11 |
12 |
13 | class TestAppLogger(unittest.TestCase):
14 |
15 | @classmethod
16 | def setUpClass(cls):
17 | cls.valid_config = {
18 | "log_level": "DEBUG",
19 | "logging_enabled": "true",
20 | "app_insights_key": test_instrumentation_key,
21 | }
22 | cls.invalid_config = {
23 | "log_level": "DEBUG",
24 | "logging_enabled": "false",
25 | "app_insights_key": test_invalid_instrumentation_key,
26 | }
27 |
28 |
29 | def test_logger_creation_valid_instrumentation_key(self):
30 | """Test with valid formatted instrumentation key."""
31 | global test_instrumentation_key
32 | try:
33 | app_logger = AppLogger(
34 | config=self.valid_config,
35 | )
36 | assert app_logger is not None
37 | except Exception:
38 | assert False
39 |
40 |
41 | def test_logger_creation_invalid_instrumentation_key(self):
42 | """Test with invalid instrumentation key."""
43 | global test_invalid_instrumentation_key
44 | with self.assertRaises(Exception):
45 | logging.disable(logging.CRITICAL)
46 | app_logger = AppLogger(
47 | config=self.invalid_config,
48 | )
49 | app_logger.get_logger()
50 | assert app_logger is not None
51 |
52 |
53 | def test_logger_creation_no_instrumentation_key(self):
54 | """Test with no instrumentation key."""
55 | with self.assertRaises(Exception):
56 | logging.disable(logging.CRITICAL)
57 | config = {"log_level": logging.DEBUG, "logging_enabled": "false"}
58 | app_logger = AppLogger(config=config)
59 | app_logger.get_logger()
60 | assert app_logger is not None
61 |
62 |
63 | def test_logging(self):
64 | """Test to use logging functions."""
65 | global test_instrumentation_key
66 | try:
67 | component_name = "TestComponent"
68 | app_logger = AppLogger(config=self.valid_config)
69 | assert app_logger is not None
70 | test_logger = app_logger.get_logger(
71 | component_name=component_name,
72 | )
73 |
74 | assert test_logger is not None
75 | test_logger.info("Test Logging")
76 | except Exception:
77 | assert False
78 |
79 | def test_event_logging(self):
80 | """Test to use event logging functions."""
81 | global test_instrumentation_key
82 | try:
83 | component_name = "TestComponent"
84 | app_logger = AppLogger(config=self.valid_config)
85 | assert app_logger is not None
86 | test_event_logger = app_logger.get_event_logger(
87 | component_name=component_name,
88 | )
89 |
90 | assert test_event_logger is not None
91 | test_event_logger.info("Test Event Logging")
92 | except Exception:
93 | assert False
94 |
95 | def test_tracing(self):
96 | """Test for Tracer."""
97 | global test_instrumentation_key
98 | try:
99 | component_name = "TestComponent"
100 | app_logger = AppLogger(config=self.valid_config)
101 | assert app_logger is not None
102 |
103 | tracer = app_logger.get_tracer(
104 | component_name=component_name,
105 | )
106 | test_logger = app_logger.get_logger(
107 | component_name=component_name,
108 | )
109 |
110 | assert test_logger is not None
111 | assert tracer is not None
112 |
113 | with tracer.span(name="testspan"):
114 | test_logger.info("in test span")
115 | except Exception:
116 | assert False
117 |
118 |
119 | def test_exception(self):
120 | """Test for calling logger.exception method."""
121 | global test_instrumentation_key
122 | try:
123 | component_name = "TestComponent"
124 | app_logger = AppLogger(
125 | config=self.valid_config,
126 | )
127 | assert app_logger is not None
128 |
129 | test_logger = app_logger.get_logger(
130 | component_name=component_name,
131 | )
132 | assert test_logger is not None
133 | try:
134 | logging.disable(logging.CRITICAL)
135 | raise Exception("Testing exception logging")
136 | except Exception as exp:
137 | test_logger.exception(exp)
138 | except Exception:
139 | assert False
140 |
141 |
142 | def test_logging_level(self):
143 | """Test for changing logger level in config."""
144 | try:
145 | global test_instrumentation_key
146 | component_name = "TestComponent"
147 | valid_config = self.valid_config.copy()
148 | valid_config["log_level"] = logging.ERROR
149 | app_logger = AppLogger(
150 | config=valid_config,
151 | )
152 | assert app_logger.config["log_level"] == logging.ERROR
153 | test_logger = app_logger.get_logger(
154 | component_name=component_name,
155 | )
156 |
157 | test_logger.error("Testing logging level")
158 | except Exception:
159 | assert False
160 |
161 |
162 | def test_logging_extra_params(self):
163 | """Test logging extra params."""
164 | try:
165 | global test_instrumentation_key
166 | component_name = "TestComponent"
167 | app_logger = AppLogger(
168 | config=self.valid_config,
169 | )
170 | test_logger = app_logger.get_logger(
171 | component_name=component_name,
172 | )
173 | extra_params = {"custom_dimensions": {"key1": "value1"}}
174 | test_logger.info("Logging extra params", extra=extra_params)
175 | except Exception:
176 | assert False
177 |
178 | def test_event_logging_extra_params(self):
179 | """Test event logging extra params."""
180 | try:
181 | global test_instrumentation_key
182 | component_name = "TestComponent"
183 | app_logger = AppLogger(
184 | config=self.valid_config,
185 | )
186 | test_event_logger = app_logger.get_event_logger(
187 | component_name=component_name,
188 | )
189 | extra_params = {"custom_dimensions": {"key1": "value1"}}
190 | test_event_logger.info("Event Logging with extra params", extra=extra_params)
191 | except Exception:
192 | assert False
193 |
194 | def test_disabled_logger(self):
195 | """Test disabled logger."""
196 | try:
197 |
198 | def do_work(app_logger=get_disabled_logger()):
199 | component_name = "TestComponent"
200 | test_logger = app_logger.get_logger(
201 | component_name=component_name,
202 | )
203 | extra_params = {"custom_dimensions": {"key1": "value1"}}
204 | test_logger.info("Logging extra params", extra=extra_params)
205 |
206 | do_work()
207 | except Exception:
208 | assert False
209 |
210 |
211 | if __name__ == '__main__':
212 | unittest.main()
213 |
214 |
--------------------------------------------------------------------------------
/azure_monitor/simple_sample/customDimensions.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 |
4 | from opencensus.ext.azure.log_exporter import AzureLogHandler
5 |
6 | logger = logging.getLogger(__name__)
7 |
8 | appKey = os.getenv('APPLICATIONINSIGHTS_CONNECTION_STRING')
9 |
10 | logger.addHandler(AzureLogHandler(
11 | connection_string=appKey)
12 | )
13 |
14 | properties = {'custom_dimensions': {'key_1': 'value_1', 'key_2': 'value_2'}}
15 |
16 | # Use properties in logging statements
17 | logger.warning('action', extra=properties)
--------------------------------------------------------------------------------
/azure_monitor/simple_sample/database.py:
--------------------------------------------------------------------------------
1 | import pyodbc
2 | import os
3 |
4 | server = os.getenv('DB_SERVER')
5 | database = os.getenv('DB_DATABASE')
6 | username = os.getenv('DB_USERNAME')
7 | password = os.getenv('DB_PASSWORD')
8 | driver= '{ODBC Driver 17 for SQL Server}'
9 |
10 | with pyodbc.connect('DRIVER='+driver+';SERVER=tcp:'+server+';PORT=1433;DATABASE='+database+';UID='+username+';PWD='+ password) as conn:
11 | with conn.cursor() as cursor:
12 | cursor.execute("SELECT TOP 3 name, collation_name FROM sys.databases")
13 | row = cursor.fetchone()
14 | while row:
15 | print (str(row[0]) + " " + str(row[1]))
16 | row = cursor.fetchone()
--------------------------------------------------------------------------------
/azure_monitor/simple_sample/event.py:
--------------------------------------------------------------------------------
1 | # Copyright 2019, OpenCensus Authors
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import logging
16 |
17 | from opencensus.ext.azure.log_exporter import AzureEventHandler
18 |
19 | logger = logging.getLogger(__name__)
20 | # TODO: you need to specify the instrumentation key in a connection string
21 | # and place it in the APPLICATIONINSIGHTS_CONNECTION_STRING
22 | # environment variable.
23 | logger.addHandler(AzureEventHandler())
24 | logger.setLevel(logging.INFO)
25 | logger.info('Hello, World!')
26 |
--------------------------------------------------------------------------------
/azure_monitor/simple_sample/metric.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from opencensus.ext.azure import metrics_exporter
4 | from opencensus.stats import aggregation as aggregation_module
5 | from opencensus.stats import measure as measure_module
6 | from opencensus.stats import stats as stats_module
7 | from opencensus.stats import view as view_module
8 | from opencensus.tags import tag_map as tag_map_module
9 |
10 | stats = stats_module.stats
11 | view_manager = stats.view_manager
12 | stats_recorder = stats.stats_recorder
13 |
14 | appKey = os.getenv('APPLICATIONINSIGHTS_CONNECTION_STRING')
15 |
16 | CARROTS_MEASURE = measure_module.MeasureInt("carrots",
17 | "number of carrots",
18 | "carrots")
19 | CARROTS_VIEW = view_module.View("carrots_view",
20 | "number of carrots",
21 | [],
22 | CARROTS_MEASURE,
23 | aggregation_module.CountAggregation())
24 |
25 | # Enable metrics
26 | # Set the interval in seconds in which you want to send metrics
27 | exporter = metrics_exporter.new_metrics_exporter(connection_string=appKey)
28 | view_manager.register_exporter(exporter)
29 |
30 | view_manager.register_view(CARROTS_VIEW)
31 | mmap = stats_recorder.new_measurement_map()
32 | tmap = tag_map_module.TagMap()
33 |
34 | def prompt():
35 | mmap.measure_int_put(CARROTS_MEASURE, 1000)
36 | mmap.record(tmap)
37 | # Default export interval is every 15.0s
38 |
39 | def main():
40 | while True:
41 | prompt()
42 |
43 | print("Done recording metrics")
44 |
45 | if __name__ == "__main__":
46 | main()
--------------------------------------------------------------------------------
/azure_monitor/simple_sample/metric2.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from datetime import datetime
4 | from opencensus.ext.azure import metrics_exporter
5 | from opencensus.stats import aggregation as aggregation_module
6 | from opencensus.stats import measure as measure_module
7 | from opencensus.stats import stats as stats_module
8 | from opencensus.stats import view as view_module
9 | from opencensus.tags import tag_map as tag_map_module
10 |
11 | appKey = os.getenv('APPLICATIONINSIGHTS_CONNECTION_STRING')
12 |
13 | stats = stats_module.stats
14 | view_manager = stats.view_manager
15 | stats_recorder = stats.stats_recorder
16 |
17 | prompt_measure = measure_module.MeasureInt("prompts",
18 | "number of prompts",
19 | "prompts")
20 | prompt_view = view_module.View("prompt_view",
21 | "number of prompts",
22 | [],
23 | prompt_measure,
24 | aggregation_module.CountAggregation())
25 | exporter = metrics_exporter.new_metrics_exporter(connection_string=appKey)
26 |
27 | view_manager.register_view(prompt_view)
28 | mmap = stats_recorder.new_measurement_map()
29 | tmap = tag_map_module.TagMap()
30 |
31 | view_manager.register_exporter(exporter)
32 |
33 | def prompt():
34 | mmap.measure_int_put(prompt_measure, 1)
35 | mmap.record(tmap)
36 | metrics = list(mmap.measure_to_view_map.get_metrics(datetime.utcnow()))
37 | print(metrics[0].time_series[0].points[0])
38 |
39 | def main():
40 | while True:
41 | prompt()
42 |
43 | if __name__ == "__main__":
44 | main()
--------------------------------------------------------------------------------
/azure_monitor/simple_sample/module1.py:
--------------------------------------------------------------------------------
1 | # module1.py
2 | import logging
3 | import os
4 |
5 | from opencensus.trace import config_integration
6 | from opencensus.trace.samplers import AlwaysOnSampler
7 | from opencensus.trace.tracer import Tracer
8 | from opencensus.ext.azure.log_exporter import AzureLogHandler
9 | from module2 import function_1
10 |
11 | config_integration.trace_integrations(['logging'])
12 | logging.basicConfig(format='%(asctime)s spanId=%(spanId)s %(message)s')
13 | tracer = Tracer(sampler=AlwaysOnSampler())
14 |
15 | logger = logging.getLogger(__name__)
16 |
17 | appKey = os.getenv('APPLICATIONINSIGHTS_CONNECTION_STRING')
18 |
19 | logger.addHandler(AzureLogHandler(
20 | connection_string=appKey)
21 | )
22 |
23 | logger.warning('Before the span')
24 |
25 | with tracer.span(name='hello'):
26 | logger.warning('In the span')
27 | function_1(tracer)
28 |
29 | logger.warning('After the span')
--------------------------------------------------------------------------------
/azure_monitor/simple_sample/module2.py:
--------------------------------------------------------------------------------
1 | # module2.py
2 |
3 | import logging
4 | import os
5 |
6 | from opencensus.trace import config_integration
7 | from opencensus.trace.samplers import AlwaysOnSampler
8 | from opencensus.trace.tracer import Tracer
9 | from opencensus.ext.azure.log_exporter import AzureLogHandler
10 |
11 | config_integration.trace_integrations(['logging'])
12 | logging.basicConfig(format='%(asctime)s spanId=%(spanId)s %(message)s')
13 | tracer = Tracer(sampler=AlwaysOnSampler())
14 |
15 | logger = logging.getLogger(__name__)
16 |
17 | appKey = os.getenv('APPLICATIONINSIGHTS_CONNECTION_STRING')
18 |
19 | logger.addHandler(AzureLogHandler(
20 | connection_string=appKey)
21 | )
22 |
23 | def function_1(parent_tracer=None):
24 | if parent_tracer is not None:
25 | tracer = Tracer(
26 | span_context=parent_tracer.span_context,
27 | sampler=AlwaysOnSampler(),
28 | )
29 | else:
30 | tracer = Tracer(sampler=AlwaysOnSampler())
31 |
32 | with tracer.span("function_1"):
33 | logger.info("In function_1")
--------------------------------------------------------------------------------
/azure_monitor/simple_sample/prompt.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | from opencensus.ext.azure.log_exporter import AzureLogHandler
4 |
5 | logger = logging.getLogger(__name__)
6 |
7 | appKey = os.getenv('APPLICATIONINSIGHTS_CONNECTION_STRING')
8 |
9 | logger.addHandler(AzureLogHandler(
10 | connection_string=appKey)
11 | )
12 |
13 | # You can also instantiate the exporter directly if you have the environment variable
14 | # `APPLICATIONINSIGHTS_CONNECTION_STRING` configured
15 | # logger.addHandler(AzureLogHandler())
16 |
17 | def valuePrompt():
18 | line = input("Enter a value: ")
19 | logger.warning(line)
20 |
21 | def main():
22 | while True:
23 | valuePrompt()
24 |
25 | if __name__ == "__main__":
26 | main()
--------------------------------------------------------------------------------
/azure_monitor/simple_sample/readme.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Sample: Simple Python Applications with OpenCensus'
3 | description: Simple Python Applications with OpenCensus.
4 | author: givenscj
5 | ms.author: givenscj
6 | ms.devlang: python
7 | ms.topic: sample
8 | ms.date: 07/27/2022
9 | ms.custom: []
10 | ---
11 |
12 | # Sample: Simple Python Applications with OpenCensus
13 |
14 | The following steps walkthrough setting up simple Python examples and executing them in a local environment.
15 |
16 | ## 1 - Environment Setup
17 |
18 | Follow the steps in the [setup documentation](/azure_monitor/README.md).
19 |
20 | ## 2 - Create a simple Python App
21 |
22 | - Create a new python file called `./simple_sample/app.py`.
23 | - Copy the following into it, be sure to replace your Application Insights connection string that you copied above:
24 |
25 | ```python
26 | import logging
27 |
28 | from opencensus.ext.azure.log_exporter import AzureLogHandler
29 |
30 | logger = logging.getLogger(__name__)
31 |
32 | logger.addHandler(AzureLogHandler(connection_string='InstrumentationKey='))
33 |
34 | logger.warning('Hello, World!')
35 | ```
36 |
37 | - Press **F5** to run the file, select **Python file** in the debug configuration window.
38 | - If you get an error about `psutil._psutil_windows` do the following:
39 | - Open the `.venv\Lib\site-packages` folder and delete the `psutil` and `psutil-5.9.1.dist-info` folders.
40 | - Then run the following:
41 |
42 | ```Python
43 | python -m pip install --upgrade pip
44 | python -m pip install psutil
45 | ```
46 |
47 | - Switch to the Azure Portal, navigate to your Application Insights resource.
48 | - Under **Monitoring**, select **Logs**.
49 | - Run the following kusto query:
50 |
51 | ```kusto
52 | traces
53 | | sort by timestamp
54 | | where cloud_RoleName == "app.py"
55 | ```
56 |
57 | - You should see the following:
58 |
59 | 
60 |
61 | - Open the `./azure_monitor/simple_sample/metric.py` file.
62 | - Press **F5** to run the file, select **Python file** in the debug configuration window.
63 | - Switch to the Azure Portal.
64 | - Browse to your lab resource group.
65 | - Browse to the `python-appinsights-SUFFIX` application insights resource and select it.
66 | - Under **Monitoring**, select **Metrics**.
67 | - For the **Metric namespace**, select **carrots_view**.
68 | - For the **Metric**, select **carrots_view**.
69 | - You should see some data displayed:
70 |
71 | 
72 |
73 | - Stop the application.
74 |
75 | - You can user kusto queries to get metric data:
76 | - Under **Montioring**, select **Logs**.
77 | - Run the following query:
78 |
79 | ```kusto
80 | customMetrics
81 | | where cloud_RoleName == "metric.py"
82 | ```
83 |
84 | - You should see the metric value is recorded every 15 seconds:
85 |
86 | 
87 |
88 | ## 3 - Sending trace data
89 |
90 | - Open the `./azure_monitor/simple_sample/trace.py` file, notice that the connection string is being pulled from an environment variable rather than being hard coded.
91 | - Press **F5** to run the file.
92 | - Switch to the Azure Portal.
93 | - Browse to your lab resource group.
94 | - Browse to the `python-appinsights-SUFFIX` application insights resource and select it.
95 | - Under **Monitoring**, select **Logs**.
96 | - Run the following Kusto query to see the `traces` sent to application insights:
97 |
98 | ```kql
99 | traces
100 | | sort by timestamp
101 | | where cloud_RoleName == "trace.py"
102 | ```
103 |
104 | - You should see your trace in the **message** column:
105 |
106 | 
107 |
108 | - For your trace item notice that some of the columns are empty, but others (such as `cloud_RoleName`, `cloud_RoleInstance` and `client_*`) are populated based on the client information from the SDK.
109 |
110 | ## 4 - Capture exceptions and custom dimensions
111 |
112 | - Switch back to your Visual Studio Code window.
113 | - Select the `./simple_sample/properties.py` file.
114 | - Press **F5** to run the file, select **Python file** in the debug configuration window.
115 | - Switch to the Azure Portal.
116 | - Browse to your lab resource group.
117 | - Browse to the `python-appinsights-SUFFIX` application insights resource and select it.
118 | - Under **Monitoring**, select **Logs**.
119 | - Run the following Kusto query to see the `exception` sent to application insights:
120 |
121 | ```kql
122 | exceptions
123 | | sort by timestamp
124 | | where cloud_RoleName == "properties.py"
125 | ```
126 |
127 | - Expand your exception item, notice the `details` column has the exception details you would expect, but also expand the `customDimensions` column to review the custom data that was sent to the application insights table.
128 |
129 | 
--------------------------------------------------------------------------------
/azure_monitor/simple_sample/spanComplex.py:
--------------------------------------------------------------------------------
1 | # module1.py
2 | import logging
3 |
4 | from opencensus.trace import config_integration
5 | from opencensus.trace.samplers import AlwaysOnSampler
6 | from opencensus.trace.tracer import Tracer
7 | from module2 import function_1
8 |
9 | config_integration.trace_integrations(['logging'])
10 | logging.basicConfig(format='%(asctime)s traceId=%(traceId)s spanId=%(spanId)s %(message)s')
11 | tracer = Tracer(sampler=AlwaysOnSampler())
12 |
13 | logger = logging.getLogger(__name__)
14 | logger.warning('Before the span')
15 | with tracer.span(name='hello'):
16 | logger.warning('In the span')
17 | function_1(tracer)
18 | logger.warning('After the span')
19 |
20 | # module2.py
21 |
22 | import logging
23 |
24 | from opencensus.trace import config_integration
25 | from opencensus.trace.samplers import AlwaysOnSampler
26 | from opencensus.trace.tracer import Tracer
27 |
28 | config_integration.trace_integrations(['logging'])
29 | logging.basicConfig(format='%(asctime)s traceId=%(traceId)s spanId=%(spanId)s %(message)s')
30 | tracer = Tracer(sampler=AlwaysOnSampler())
31 |
32 | def function_1(parent_tracer=None):
33 | if parent_tracer is not None:
34 | tracer = Tracer(
35 | span_context=parent_tracer.span_context,
36 | sampler=AlwaysOnSampler(),
37 | )
38 | else:
39 | tracer = Tracer(sampler=AlwaysOnSampler())
40 |
41 | with tracer.span("function_1"):
42 | logger.info("In function_1")
--------------------------------------------------------------------------------
/azure_monitor/simple_sample/spanSimple.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 |
4 | from opencensus.trace import config_integration
5 | from opencensus.trace.samplers import AlwaysOnSampler
6 | from opencensus.trace.tracer import Tracer
7 | from opencensus.ext.azure.log_exporter import AzureLogHandler
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 | appKey = os.getenv('APPLICATIONINSIGHTS_CONNECTION_STRING')
12 |
13 | logger.addHandler(AzureLogHandler(
14 | connection_string=appKey)
15 | )
16 |
17 | config_integration.trace_integrations(['logging'])
18 | #logging.basicConfig(format='%(asctime)s traceId=%(traceId)s spanId=%(spanId)s %(message)s')
19 | tracer = Tracer(sampler=AlwaysOnSampler())
20 |
21 | logger.warning('Before the span')
22 |
23 | with tracer.span(name='hello'):
24 | logger.warning('In the span')
25 |
26 | logger.warning('After the span')
--------------------------------------------------------------------------------
/azure_monitor/simple_sample/trace.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 |
4 | from opencensus.ext.azure.log_exporter import AzureLogHandler
5 |
6 | appKey = os.getenv('APPLICATIONINSIGHTS_CONNECTION_STRING')
7 |
8 | logger = logging.getLogger(__name__)
9 | logger.addHandler(AzureLogHandler(connection_string=appKey))
10 | logger.warning('Hello, World!')
11 |
12 | logger.addHandler
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = opencensus-azure-monitor-samples
3 | description = Azure Monitor exporter samples for OpenCensus
4 | long_description = file: README.md
5 | long_description_content_type = text/markdown
6 | author = Microsoft
7 | author_email = appinsightssdk@microsoft.com
8 | url = https://github.com/Azure-Samples/azure-monitor-opencensus-python
9 | platforms = any
10 | license = MIT
11 | classifiers =
12 | Intended Audience :: End Users/Desktop
13 | Development Status :: 5 - Production/Stable
14 | Intended Audience :: End Users/Desktop
15 | License :: OSI Approved :: Apache Software License
16 | Programming Language :: Python
17 | Programming Language :: Python :: 2
18 | Programming Language :: Python :: 2.7
19 | Programming Language :: Python :: 3
20 | Programming Language :: Python :: 3.4
21 | Programming Language :: Python :: 3.5
22 | Programming Language :: Python :: 3.6
23 | Programming Language :: Python :: 3.7
24 |
25 | [options]
26 | package_dir=
27 | =flask_sample
28 | packages=find_namespace:
29 | install_requires =
30 | flask-sqlalchemy >= 2.4.1
31 | flask-wtf >= 0.14.3
32 | opencensus-ext-azure >= 1.0.2
33 | opencensus-ext-flask >= 0.7.3
34 | opencensus-ext-requests >= 0.7.3
35 | opencensus-ext-sqlalchemy >= 0.1.2
36 |
37 | [options.packages.find]
38 | where = flask_sample
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import setuptools
4 |
5 | BASE_DIR = os.path.dirname(__file__)
6 | VERSION_FILENAME = os.path.join(BASE_DIR, "version.py")
7 | PACKAGE_INFO = {}
8 | with open(VERSION_FILENAME) as f:
9 | exec(f.read(), PACKAGE_INFO)
10 |
11 | setuptools.setup(version=PACKAGE_INFO["__version__"])
--------------------------------------------------------------------------------
/version.py:
--------------------------------------------------------------------------------
1 | __version__ = '0.1.dev0'
2 |
3 |
--------------------------------------------------------------------------------