├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .env.sample.txt ├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── images ├── custom_metrics.png ├── custom_properties.png ├── fastapi_clubs.png └── opencensus_application_insights.png ├── requirements.txt ├── setup.py ├── src └── main.py ├── tests ├── __init__.py └── test.py └── version.py /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.177.0/containers/python-3/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6 4 | ARG VARIANT="3.9" 5 | FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} 6 | 7 | # [Option] Install Node.js 8 | ARG INSTALL_NODE="true" 9 | ARG NODE_VERSION="lts/*" 10 | RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 11 | 12 | # [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. 13 | # COPY requirements.txt /tmp/pip-tmp/ 14 | # RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ 15 | # && rm -rf /tmp/pip-tmp 16 | 17 | # [Optional] Uncomment this section to install additional OS packages. 18 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 19 | # && apt-get -y install --no-install-recommends 20 | 21 | # [Optional] Uncomment this line to install global node packages. 22 | # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.177.0/containers/python-3 3 | { 4 | "name": "Python 3", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | "context": "..", 8 | "args": { 9 | // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9 10 | "VARIANT": "3.8", 11 | // Options 12 | "INSTALL_NODE": "false", 13 | "NODE_VERSION": "lts/*" 14 | } 15 | }, 16 | 17 | // Set *default* container specific settings.json values on container create. 18 | "settings": { 19 | "terminal.integrated.shell.linux": "/bin/bash", 20 | "python.pythonPath": "/usr/local/bin/python", 21 | "python.languageServer": "Pylance", 22 | "python.linting.enabled": true, 23 | "python.linting.pylintEnabled": true, 24 | "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", 25 | "python.formatting.blackPath": "/usr/local/py-utils/bin/black", 26 | "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", 27 | "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", 28 | "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", 29 | "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", 30 | "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", 31 | "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", 32 | "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" 33 | }, 34 | 35 | // Add the IDs of extensions you want installed when the container is created. 36 | "extensions": [ 37 | "ms-python.python", 38 | "ms-python.vscode-pylance" 39 | ], 40 | 41 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 42 | // "forwardPorts": [], 43 | 44 | // Use 'postCreateCommand' to run commands after the container is created. 45 | "postCreateCommand": "pip3 install --user -r requirements.txt", 46 | 47 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 48 | "remoteUser": "vscode" 49 | } 50 | -------------------------------------------------------------------------------- /.env.sample.txt: -------------------------------------------------------------------------------- 1 | APPINSIGHTS_INSTRUMENTATIONKEY=YOUR_API_KEY -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | ``` 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### Versions 28 | > 29 | 30 | ### Mention any other details that might be useful 31 | 32 | > --------------------------------------------------------------- 33 | > Thanks! We'll be in touch soon. 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Feature 19 | [ ] Code style update (formatting, local variables) 20 | [ ] Refactoring (no functional changes, no api changes) 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Remove local folder and files 7 | /venv 8 | /myproject.egg-info 9 | /.vscode 10 | .env 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to [project-title] 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | languages: 4 | - python 5 | products: 6 | - azure 7 | name: Use OpenCensus with Python FAST API and Azure Monitor 8 | description: "Using OpenCensus with Python FastAPI and Azure Monitor for logging and tracing" 9 | urlFragment: "opencensus-with-fastapi-and-azure-monitor" 10 | --- 11 | 12 | 13 | # Using OpenCensus with Python FAST API and Azure Monitor 14 | 15 | Azure Monitor supports distributed tracing, metric collection, and logging of Python applications through integration with [OpenCensus](https://docs.microsoft.com/en-US/azure/azure-monitor/app/opencensus-python). 16 | 17 | This sample application walks you through the process of setting up OpenCensus for Python [FAST API](https://fastapi.tiangolo.com/tutorial) and sending [monitoring data](https://docs.microsoft.com/en-us/azure/azure-monitor/app/opencensus-python#telemetry-type-mappings) to Azure Monitor for logging and tracing. 18 | 19 | ### Tracing 20 | 21 | * Hitting an endpoint in the FastAPI application will be treated as an incoming requests (`requests` table in Azure Monitor). 22 | 23 | ### Logging 24 | 25 | * A log message is also sent every time a function is called (`traces` table in Azure Monitor). 26 | * An exception telemetry is sent when an invalid task is entered (established date equals to 0). 27 | 28 | ### Metrics (partially supported via OpenCensus.stats) 29 | 30 | * Custom metrics are implemented on a specific function in the sample (customMetrics table in Azure Monitor). 31 | 32 | **NOTE:** 33 | `OpenCensus.stats` supports 4 aggregation methods but provides partial support for Azure Monitor. `Distribution` aggregation for example, is NOT supported by the Azure Exporter. 34 | 35 | **NOTE:** log_level sets the threshold for logger to ["selected level"](https://docs.python.org/3/library/logging.html#logging.Logger.setLevel), In this sample [logger level](https://docs.python.org/3/library/logging.html#logging.Logger.setLevel) is set to `info`. So all traces will reflecting into Azure Monitor. 36 | 37 | ```python 38 | if __name__=="__main__": 39 | print("main started") 40 | uvicorn.run("main:app", port=8000, log_level="info") 41 | ``` 42 | 43 | You can reach out to all options of `uvicorn` in [Uvicorn deployment documentation](https://www.uvicorn.org/deployment/#running-from-the-command-line) 44 | 45 | ## Pre-requisites 46 | 47 | * Python 3.x 48 | * Microsoft Azure Account 49 | 50 | ## 1. Running Clubs API project 51 | 52 | 1. Create a virtual environment. 53 | 54 | ```bash 55 | python -m venv .venv 56 | ``` 57 | 58 | 2. Activate the virtual environment you just created. 59 | 60 | ```bash 61 | source .venv/bin/activate 62 | ``` 63 | 64 | 3. Rename the `.env.sample.txt` file to `.env` 65 | This contains the environment variables that you need to set for your Azure Storage account. 66 | 67 | 4. Set Application Insights Instrumentation Key variables in the `.env` file: 68 | `APPINSIGHTS_INSTRUMENTATIONKEY` 69 | 70 | 5. Install the dependencies. 71 | 72 | ```bash 73 | pip install -r requirements.txt 74 | ``` 75 | 76 | 6. Run the project! 77 | 78 | ```bash 79 | python src/main.py 80 | ``` 81 | 82 | Visit [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) for Swagger UI 83 | 84 | `main.py` Swagger UI: 85 | ![fastapi clubs](images/fastapi_clubs.png) 86 | 87 | 1. GET / 88 | 89 | ```bash 90 | curl -X 'GET' 'http://127.0.0.1:8000' -H 'accept: application/json' -H 'Content-Type: application/json' 91 | ``` 92 | 93 | result: 94 | 95 | ```bash 96 | "Clubs API" 97 | ``` 98 | 99 | ## Create Telemetry data 100 | 101 | 1. Create a new club `POST /clubs` 102 | 103 | ```bash 104 | curl -X 'POST' \ 105 | 'http://127.0.0.1:8000/clubs' \ 106 | -H 'accept: application/json' \ 107 | -H 'Content-Type: application/json' \ 108 | -d '{ 109 | "name": "Fenerbahce", 110 | "country": "Turkey", 111 | "established": 1907 112 | }' 113 | ``` 114 | 115 | ```bash 116 | curl -X 'POST' \ 117 | 'http://127.0.0.1:8000/clubs' \ 118 | -H 'accept: application/json' \ 119 | -H 'Content-Type: application/json' \ 120 | -d '{ 121 | "name": "Manchester United", 122 | "country": "United Kingdom", 123 | "established": 1878 124 | }' 125 | ``` 126 | 127 | The following code in `create_club` function creates a custom property in Azure Monitor. 128 | 129 | ```python 130 | ## CustomDimentions 131 | properties = {'custom_dimensions': club_db[-1] } 132 | logger.warning('club record is added', extra=properties) 133 | ``` 134 | 135 | Custom dimension result will look like below: 136 | ![custom dimensions azure monitor](images/custom_properties.png) 137 | 138 | 2. Create an exception with wrong type: 139 | POST /clubs 140 | 141 | ```bash 142 | curl -X 'POST' \ 143 | 'http://127.0.0.1:8000/clubs' \ 144 | -H 'accept: application/json' \ 145 | -H 'Content-Type: application/json' \ 146 | -d '{ 147 | "name": "Arsenal", 148 | "country": "United Kingdom", 149 | "established": "a" 150 | }' 151 | ``` 152 | 153 | result will be: 154 | 155 | ```bash 156 | {"detail":[{"loc":["body","established"],"msg":"value is not a valid integer","type":"type_error.integer"}]} 157 | ``` 158 | 159 | 3. Create an exception with invalid establish date: 160 | POST /clubs 161 | 162 | ```bash 163 | curl -X 'POST' \ 164 | 'http://127.0.0.1:8000/clubs' \ 165 | -H 'accept: application/json' \ 166 | -H 'Content-Type: application/json' \ 167 | -d '{ 168 | "name": "Arsenal", 169 | "country": "United Kingdom", 170 | "established": 0 171 | }' 172 | ``` 173 | 174 | result will be: 175 | 176 | ```bash 177 | "Internal Server Error" 178 | ``` 179 | 180 | 4. GET /clubs 181 | 182 | ```bash 183 | curl -X 'GET' 'http://127.0.0.1:8000/clubs' -H 'accept: application/json' -H 'Content-Type: application/json' 184 | ``` 185 | 186 | 5. DELETE /clubs 187 | 188 | ```bash 189 | curl -X 'DELETE' \ 190 | 'http://127.0.0.1:8000/clubs/Fenerbahce' \ 191 | -H 'accept: application/json' 192 | ``` 193 | 194 | 6. Let's create another exception with DELETE /clubs 195 | 196 | ```bash 197 | curl -X 'DELETE' \ 198 | 'http://127.0.0.1:8000/clubs/Chelsea' \ 199 | -H 'accept: application/json' 200 | ``` 201 | 202 | Since this record is not available we get a result like below: 203 | 204 | ```bash 205 | "An error accured while deleting club id:[], Chelsea" 206 | ``` 207 | 208 | 7. In this scenario, in addition to Logs and Traces, let's generate some custom metrics and push into Azure Monitor using OpenCensus stats. Stats are partially supported in Azure Monitor for more information visit [OpenCensus Python Metrics - Microsoft Docs](https://docs.microsoft.com/en-US/azure/azure-monitor/app/opencensus-python#metrics) 209 | 210 | GET /log_custom_metric 211 | 212 | ```bash 213 | curl -X 'GET' 'http://127.0.0.1:8000/log_custom_metric' -H 'accept: application/json' -H 'Content-Type: application/json' 214 | ``` 215 | 216 | After genering these custom metrics, we can view these in `customMetrics` in Azure Monitor. 217 | 218 | ![custom metrics](images/custom_metrics.png) 219 | 220 | Results will look like below on Azure Monitor: 221 | ![opencensus application insights](images/opencensus_application_insights.png) 222 | 223 | ## 3. Running Tests 224 | 225 | `pytest tests/test.py` 226 | 227 | ```bash 228 | $ pytest tests/test.py 229 | =========================== test session starts ================================= 230 | platform win32 -- Python 3.8.7, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 231 | rootdir: C:\Users\username\opencensus-with-fastapi-and-azure-monitor 232 | collected 7 items 233 | 234 | tests\test.py ...... [100%] 235 | ================================ 7 passed in 0.48s =============================== 236 | ``` 237 | 238 | ## License 239 | 240 | See [LICENSE](LICENSE). 241 | 242 | ## Code of Conduct 243 | 244 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 245 | 246 | ## Contributing 247 | 248 | See [CONTRIBUTING](CONTRIBUTING). 249 | -------------------------------------------------------------------------------- /images/custom_metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/opencensus-with-fastapi-and-azure-monitor/ab6c488c5ad677593ffd19d034f3935343de4155/images/custom_metrics.png -------------------------------------------------------------------------------- /images/custom_properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/opencensus-with-fastapi-and-azure-monitor/ab6c488c5ad677593ffd19d034f3935343de4155/images/custom_properties.png -------------------------------------------------------------------------------- /images/fastapi_clubs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/opencensus-with-fastapi-and-azure-monitor/ab6c488c5ad677593ffd19d034f3935343de4155/images/fastapi_clubs.png -------------------------------------------------------------------------------- /images/opencensus_application_insights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/opencensus-with-fastapi-and-azure-monitor/ab6c488c5ad677593ffd19d034f3935343de4155/images/opencensus_application_insights.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | opencensus 3 | opencensus-ext-azure 4 | opencensus-ext-logging 5 | uvicorn 6 | python-dotenv 7 | pytest -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name='azopencensus', version='1.0', packages=find_packages()) -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | # Opencensus Azure imports 2 | from opencensus.trace.attributes_helper import COMMON_ATTRIBUTES 3 | from opencensus.ext.azure.trace_exporter import AzureExporter 4 | from opencensus.trace.samplers import ProbabilitySampler 5 | from opencensus.trace.tracer import Tracer 6 | from opencensus.trace.span import SpanKind 7 | from opencensus.trace.status import Status 8 | from opencensus.trace import config_integration 9 | from opencensus.ext.azure.log_exporter import AzureLogHandler 10 | 11 | #FastAPI & Other imports 12 | from fastapi import FastAPI, Request 13 | from dotenv import load_dotenv 14 | from datetime import datetime 15 | import logging, time, os, uvicorn 16 | from pydantic import BaseModel 17 | 18 | # Metric imports 19 | from opencensus.ext.azure import metrics_exporter 20 | from opencensus.stats import aggregation as aggregation_module 21 | from opencensus.stats import measure as measure_module 22 | from opencensus.stats import stats as stats_module 23 | from opencensus.stats import view as view_module 24 | from opencensus.tags import tag_map as tag_map_module 25 | 26 | logger = logging.getLogger(__name__) 27 | 28 | app = FastAPI() 29 | 30 | club_db = [] 31 | 32 | class Club(BaseModel): 33 | name: str 34 | country: str 35 | established: int 36 | 37 | # load en vars 38 | load_dotenv() 39 | 40 | # get instrumentation key 41 | APPINSIGHTS_INSTRUMENTATIONKEY = os.environ["APPINSIGHTS_INSTRUMENTATIONKEY"] 42 | 43 | HTTP_URL = COMMON_ATTRIBUTES['HTTP_URL'] 44 | HTTP_STATUS_CODE = COMMON_ATTRIBUTES['HTTP_STATUS_CODE'] 45 | 46 | @app.on_event("startup") 47 | async def startup_event(): 48 | print('using temporary directory:') 49 | config_integration.trace_integrations(['logging']) 50 | logger = logging.getLogger(__name__) 51 | 52 | handler = AzureLogHandler(connection_string=f'InstrumentationKey={APPINSIGHTS_INSTRUMENTATIONKEY}') 53 | logger.addHandler(handler) 54 | 55 | @app.on_event('shutdown') 56 | async def shutdown_event(): 57 | await get_http_client_session().close() 58 | 59 | @app.middleware("http") 60 | async def add_process_time_header(request: Request, call_next): 61 | tracer = Tracer(exporter=AzureExporter(connection_string=f'InstrumentationKey={APPINSIGHTS_INSTRUMENTATIONKEY}'),sampler=ProbabilitySampler(1.0)) 62 | with tracer.span("main") as span: 63 | span.span_kind = SpanKind.SERVER 64 | 65 | response = await call_next(request) 66 | 67 | tracer.add_attribute_to_current_span( 68 | attribute_key=HTTP_STATUS_CODE, 69 | attribute_value=response.status_code) 70 | tracer.add_attribute_to_current_span( 71 | attribute_key=HTTP_URL, 72 | attribute_value=str(request.url)) 73 | 74 | return response 75 | 76 | @app.get("/") 77 | async def root(request:Request): 78 | message="Clubs API" 79 | print(message) 80 | logger.warning(message) 81 | return message 82 | 83 | # Create Club details 84 | @app.post('/clubs') 85 | def create_club(club:Club): 86 | message="creating club" 87 | print(message) 88 | logger.info(message) 89 | 90 | try: 91 | # This is scenario to throw an error in code when you provide 0 as established date 92 | print(1/club.established) 93 | 94 | # Append club 95 | club_db.append(club.dict()) 96 | 97 | ## CustomDimensions 98 | properties = {'custom_dimensions': club_db[-1] } 99 | print(properties) 100 | logger.warning('club record is added', extra=properties) 101 | 102 | return club_db[-1] 103 | except: 104 | # log exception when there's an error 105 | error_message = "An error occured while creating club" 106 | logger.error(error_message) 107 | logger.exception(error_message) 108 | return error_message 109 | 110 | # Get All Clubs 111 | @app.get('/clubs') 112 | def get_clubs(): 113 | message="get all clubs" 114 | print(message) 115 | logger.info(message) 116 | return club_db 117 | 118 | # Delete a club 119 | @app.delete('/clubs/{club_name}') 120 | def delete_club(club_name: str): 121 | message="deleting club" 122 | print(message) 123 | logger.info(message) 124 | # find club index by club name 125 | club_index = [i for i,x in enumerate(club_db) if x['name'] == club_name] 126 | try: 127 | club_db.pop(club_index[0]) 128 | logger.warning(f'club record is deleted id:{club_index}, {club_name}') 129 | return club_db 130 | except: 131 | # log exception when there's an error 132 | logger.exception(f'An error occured while deleting club id:{club_index}, {club_name}') 133 | return f'An error occured while deleting club id:{club_index}, {club_name}' 134 | 135 | # Generate some custom metrics 136 | @app.get("/log_custom_metric") 137 | async def log_custom_metric(): 138 | stats = stats_module.stats 139 | view_manager = stats.view_manager 140 | stats_recorder = stats.stats_recorder 141 | 142 | loop_measure = measure_module.MeasureInt("loop", "number of loop", "loop") 143 | loop_view = view_module.View("metric name: club stats", "number of loop", [], loop_measure, aggregation_module.CountAggregation()) 144 | view_manager.register_view(loop_view) 145 | mmap = stats_recorder.new_measurement_map() 146 | tmap = tag_map_module.TagMap() 147 | 148 | for i in range(1,3): 149 | mmap.measure_int_put(loop_measure, 1) 150 | mmap.record(tmap) 151 | metrics = list(mmap.measure_to_view_map.get_metrics(datetime.utcnow())) 152 | print(metrics[0].time_series[0].points[0]) 153 | 154 | exporter = metrics_exporter.new_metrics_exporter( 155 | connection_string=f'InstrumentationKey={APPINSIGHTS_INSTRUMENTATIONKEY}') 156 | 157 | view_manager.register_exporter(exporter) 158 | return "Log custom metric" 159 | 160 | 161 | if __name__=="__main__": 162 | print("main started") 163 | uvicorn.run("main:app", port=8000, log_level="info") -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/opencensus-with-fastapi-and-azure-monitor/ab6c488c5ad677593ffd19d034f3935343de4155/tests/__init__.py -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | from fastapi.testclient import TestClient 2 | from src.main import app 3 | 4 | client = TestClient(app) 5 | 6 | # Test endpoint 7 | def test_read_main(): 8 | response = client.get("/") 9 | assert response.status_code == 200 10 | assert response.json() == "Clubs API" 11 | 12 | # Create A Club 13 | def test_create_club(): 14 | response = client.post( 15 | "/clubs", 16 | headers={"accept": "application/json", "Content-Type": "application/json"}, 17 | json={"name": "Fenerbahce","country": "Turkey","established": 1907}, 18 | ) 19 | assert response.status_code == 200 20 | assert response.json() == { 21 | "name": "Fenerbahce", 22 | "country": "Turkey", 23 | "established": 1907, 24 | } 25 | 26 | # Divide by zero error 27 | def test_create_invalid_establish_date(): 28 | response = client.post( 29 | "/clubs", 30 | headers={"accept": "application/json", "Content-Type": "application/json"}, 31 | json={"name": "Fenerbahce","country": "Turkey","established": 0}, 32 | ) 33 | print(response.json()) 34 | assert response.status_code == 200 35 | assert response.json() == "An error occured while creating club" 36 | 37 | # Provide invalid type value 38 | def test_create_invalid_established_type(): 39 | response = client.post( 40 | "/clubs", 41 | headers={"accept": "application/json", "Content-Type": "application/json"}, 42 | json={"name": "Fenerbahce","country": "Turkey","established": "a"}, 43 | ) 44 | assert response.status_code == 422 45 | assert response.json() == {"detail":[{"loc":["body","established"],"msg":"value is not a valid integer","type":"type_error.integer"}]} 46 | 47 | 48 | # delete item 49 | def test_delete_item(): 50 | response = client.delete( 51 | "/clubs/Fenerbahce", 52 | headers={"accept": "application/json"}, 53 | ) 54 | assert response.status_code == 200 55 | 56 | # non-existed item 57 | def test_delete_nonexist_item(): 58 | response = client.delete( 59 | "/clubs/Chelsea", 60 | headers={"accept": "application/json"}, 61 | ) 62 | assert response.status_code == 200 63 | assert response.json() == "An error occured while deleting club id:[], Chelsea" 64 | 65 | # Test generating custom metrics 66 | def test_log_custom_metric(): 67 | response = client.get("/log_custom_metric") 68 | assert response.status_code == 200 69 | assert response.json() == "Log custom metric" 70 | -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.dev0' --------------------------------------------------------------------------------