Skip to content

Commit d19907d

Browse files
authored
Merge pull request #8 from ks6088ts-labs/feature/issue-7_application-insights
support application insights
2 parents c91b451 + 3af8eec commit d19907d

File tree

5 files changed

+988
-282
lines changed

5 files changed

+988
-282
lines changed

docs/index.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,11 @@
1515
- [FastAPI で作成した API エンドポイントをモデル コンテキスト プロトコル (MCP) ツールとして公開してみる](https://dev.classmethod.jp/articles/fastapi-api-mcp/)
1616
- [MCP Inspector > docs](https://modelcontextprotocol.io/docs/tools/inspector)
1717
- [MCP Inspector > codes](https://github.com/modelcontextprotocol/inspector)
18+
19+
## Application Insights
20+
21+
- [Application Insights の概要 - OpenTelemetry の可観測性](https://learn.microsoft.com/ja-jp/azure/azure-monitor/app/app-insights-overview)
22+
- [open-telemetry/opentelemetry-python > Basic Trace](https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples/basic_tracer)
23+
- [FastAPI のテレメトリデータを Azure Application Insights に送る](https://qiita.com/hoto17296/items/2f366dfabdbe3d1d4e97)
24+
- [【Azure Functions】 - Application Insights のログが表示されない問題](https://zenn.dev/headwaters/articles/ff19f7e1b99b44)
25+
- [opentelemetry-instrumentation-fastapi (python) から OpenTelemetry に入門する](https://zenn.dev/taxin/articles/opentelemetry-fast-api-instrumentation-basics)

local.settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"IsEncrypted": false,
33
"Values": {
4+
"APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=YOUR_INSTRUMENTATION_KEY;IngestionEndpoint=https://japaneast-1.in.applicationinsights.azure.com/;LiveEndpoint=https://japaneast.livediagnostics.monitor.azure.com/;ApplicationId=YOUR_APPLICATION_ID",
45
"FUNCTIONS_WORKER_RUNTIME": "python",
56
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
67
"AzureWebJobsStorage": ""

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ readme = "README.md"
66
requires-python = ">=3.10"
77
dependencies = [
88
"azure-functions>=1.23.0",
9+
"azure-monitor-opentelemetry>=1.6.10",
910
"fastapi-mcp>=0.3.4",
1011
"fastapi[standard]>=0.115.12",
12+
"opentelemetry-instrumentation-fastapi>=0.52b1",
1113
]
1214

1315
[project.optional-dependencies]
@@ -52,3 +54,7 @@ show_missing = true
5254

5355
[tool.ty]
5456
environment = { python-version = "3.10" }
57+
58+
[tool.ty.rules]
59+
unresolved-attribute = "ignore" # Ignore unresolved attributes in classes
60+
possibly-unbound-attribute = "ignore" # Ignore possibly unbound attributes in classes

template_fastapi/app.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,47 @@
33
ref. https://github.com/tadata-org/fastapi_mcp/blob/v0.3.4/examples/shared/apps/items.py
44
"""
55

6+
import random
7+
import uuid
8+
from os import getenv
9+
10+
from azure.monitor.opentelemetry import configure_azure_monitor
611
from fastapi import FastAPI, HTTPException, Query
12+
from opentelemetry import trace
13+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
14+
from opentelemetry.sdk.trace import TracerProvider
15+
from opentelemetry.sdk.trace.export import (
16+
BatchSpanProcessor,
17+
ConsoleSpanExporter,
18+
)
19+
from opentelemetry.trace import Span
720
from pydantic import BaseModel
821

22+
trace.set_tracer_provider(TracerProvider())
23+
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
24+
tracer = trace.get_tracer(__name__)
25+
926
app = FastAPI()
1027

28+
# If APPLICATIONINSIGHTS_CONNECTION_STRING exists, configure Azure Monitor
29+
AZURE_CONNECTION_STRING = getenv("APPLICATIONINSIGHTS_CONNECTION_STRING")
30+
if AZURE_CONNECTION_STRING:
31+
32+
def server_request_hook(span: Span, scope: dict):
33+
if span and span.is_recording():
34+
try:
35+
# Application Insights に送るデータにユーザ ID を追加する
36+
user_id = uuid.uuid4().hex # Replace with actual user ID retrieval logic
37+
span.set_attribute("enduser.id", user_id)
38+
except KeyError:
39+
pass
40+
41+
configure_azure_monitor(
42+
connection_string=AZURE_CONNECTION_STRING,
43+
server_request_hook=server_request_hook,
44+
)
45+
FastAPIInstrumentor.instrument_app(app)
46+
1147

1248
class Item(BaseModel):
1349
id: int
@@ -124,3 +160,64 @@ async def search_items(
124160
]
125161
for item in sample_items:
126162
items_db[item.id] = item
163+
164+
165+
# Add flaky API which receives percentage of failure
166+
@app.get("/flaky/{failure_rate}", tags=["flaky"], operation_id="flaky")
167+
async def flaky(failure_rate: int):
168+
"""
169+
A flaky endpoint that simulates a failure based on the provided failure rate.
170+
171+
The failure rate is a percentage (0-100) that determines the likelihood of failure.
172+
"""
173+
if not (0 <= failure_rate <= 100):
174+
raise HTTPException(
175+
status_code=400,
176+
detail="Failure rate must be between 0 and 100",
177+
)
178+
179+
if random.randint(0, 100) < failure_rate:
180+
raise HTTPException(
181+
status_code=500,
182+
detail="Simulated failure",
183+
)
184+
185+
return {
186+
"message": "Request succeeded",
187+
}
188+
189+
190+
# Add flaky API which raises an exception
191+
@app.get("/flaky/exception", tags=["flaky"], operation_id="flaky_exception")
192+
async def flaky_exception():
193+
"""
194+
A flaky endpoint that always raises an exception.
195+
"""
196+
raise HTTPException(
197+
status_code=500,
198+
detail="Simulated exception",
199+
)
200+
201+
202+
# Add a heavy synchronous endpoint which receives milliseconds to sleep
203+
@app.get("/heavy_sync/{sleep_ms}", tags=["heavy"], operation_id="heavy_sync_with_sleep")
204+
async def heavy_sync_with_sleep(sleep_ms: int):
205+
"""
206+
A heavy synchronous endpoint that sleeps for the specified number of milliseconds.
207+
208+
This simulates a long-running synchronous operation.
209+
"""
210+
if sleep_ms < 0:
211+
raise HTTPException(
212+
status_code=400,
213+
detail="Sleep time must be a non-negative integer",
214+
)
215+
216+
import time
217+
218+
with tracer.start_as_current_span("foo"):
219+
print(f"Sleeping for {sleep_ms} milliseconds")
220+
time.sleep(sleep_ms / 1000.0)
221+
return {
222+
"message": f"Slept for {sleep_ms} milliseconds",
223+
}

0 commit comments

Comments
 (0)