Skip to content

Commit 9c25f96

Browse files
committed
python instrument demo
1 parent 6ad6136 commit 9c25f96

File tree

174 files changed

+34735
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

174 files changed

+34735
-0
lines changed

aws-lambda/handler.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import time
2+
import requests
3+
import asyncio
4+
from opentelemetry import trace
5+
from opentelemetry.sdk.trace import TracerProvider
6+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor, ConsoleSpanExporter
7+
from opentelemetry.instrumentation.aws_lambda import AwsLambdaInstrumentor
8+
9+
# Initialize OpenTelemetry tracing
10+
provider = TracerProvider()
11+
processor = SimpleSpanProcessor(ConsoleSpanExporter()) # Exports to console for debugging
12+
provider.add_span_processor(processor)
13+
trace.set_tracer_provider(provider)
14+
15+
# Instrument the tracer provider with OpenTelemetry for Lambda
16+
AwsLambdaInstrumentor().instrument_tracer_provider()
17+
18+
# Get the OpenTelemetry tracer
19+
tracer = trace.get_tracer(__name__)
20+
21+
# Function to make an HTTP GET request
22+
def get_request():
23+
url = "https://opentelemetry.io/"
24+
try:
25+
start_time = time.time()
26+
response = requests.get(url, timeout=(2, 5)) # (connection timeout, read timeout)
27+
duration = time.time() - start_time
28+
print(f"Request completed in {duration:.2f} seconds")
29+
return response.status_code
30+
except requests.Timeout:
31+
raise Exception("Request timed out")
32+
except requests.RequestException as err:
33+
raise Exception(f"HTTP Request failed: {err}")
34+
35+
# Lambda handler function
36+
def lambda_handler(event, context):
37+
try:
38+
# Start a tracing span for the Lambda function execution
39+
with tracer.start_as_current_span("LambdaFunctionExecution"):
40+
# Log the start time of the Lambda execution
41+
start_time = time.time()
42+
43+
# Make the external HTTP request
44+
result = get_request()
45+
46+
# Log the total execution time
47+
print(f"Total execution time: {time.time() - start_time:.2f} seconds")
48+
49+
# Add a custom event and attributes to the current span
50+
current_span = trace.get_current_span()
51+
current_span.add_event("CustomEvent", {"description": "This is a custom event"})
52+
current_span.set_attribute("http.status_code", result)
53+
54+
# Return the successful response
55+
return {
56+
"statusCode": 200,
57+
"body": f"Request completed with status code {result}"
58+
}
59+
60+
# Handle any exceptions and return a 400 response
61+
except Exception as error:
62+
return {
63+
"statusCode": 400,
64+
"body": str(error)
65+
}
66+

basic/README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Python APM Guide
2+
You can follow our [documentation](https://docs.middleware.io/docs/apm-configuration/python/python-apm-setup) to setup APM for your Python application.
3+
4+
[![PyPI - Version](https://img.shields.io/pypi/v/middleware-apm)](https://pypi.org/project/middleware-apm/)
5+
6+
7+
| Traces | Metrics | Profiling | Logs (App/Custom) |
8+
|:--------:|:---------:|:-----------:|:-------------------:|
9+
| Yes | Yes | Yes | Yes/Yes |
10+
11+
## Prerequisites
12+
Ensure that you have the Middleware Host Agent installed to view Python demo data on your dashboard.
13+
14+
---------------------
15+
16+
## Installing the Package
17+
Run the following commands in your terminal:
18+
### Step 1: Install Middleware APM package
19+
```shell
20+
pip install middleware-apm
21+
```
22+
23+
## Your Sample Code
24+
By using all the MW's APM functionalities like: Distributed-tracing, Logs, Metrics and Profiling, your code will look like this:
25+
```python
26+
import logging
27+
28+
from middleware import MwTracker
29+
tracker=MwTracker()
30+
31+
logging.warning("Sample Warning Log.")
32+
logging.error("Sample Error Log.", extra={'tester': 'Alex'})
33+
```
34+
## Setup middleware.ini File
35+
Setup your `middleware.ini` file, based on below features that you want to observe in your project. Place the file at the root of your project.
36+
```ini
37+
# ---------------------------------------------------------------------------
38+
# This file contains settings for the Middleware Python-APM Agent.
39+
# Here are the settings that are common to all environments.
40+
# ---------------------------------------------------------------------------
41+
42+
[middleware.common]
43+
44+
# The name of your application as service-name, as it will appear in the UI to filter out your data.
45+
service_name = Python-APM-Service
46+
47+
# This Token binds the Python Agent's data and profiling data to your account.
48+
access_token = {YOUR-ACCESS-TOKEN}
49+
50+
# The service name, where Middleware Agent is running, in case of K8s.
51+
;mw_agent_service = mw-service.mw-agent-ns.svc.cluster.local
52+
53+
# Toggle to enable/disable distributed traces for your application.
54+
collect_traces = true
55+
56+
# Toggle to enable/disable the collection of metrics for your application.
57+
collect_metrics = true
58+
59+
# Toggle to enable/disable the collection of logs for your application.
60+
collect_logs = true
61+
62+
# Toggle to enable/disable the collection of profiling data for your application.
63+
collect_profiling = true
64+
65+
# ---------------------------------------------------------------------------
66+
```
67+
#### Note: You need to replace <strong>\{YOUR-ACCESS-TOKEN\}</strong> with your APM's Access Token.
68+
69+
70+
## Setup Virtual Environment
71+
```
72+
python -m venv newenv
73+
```
74+
```
75+
source newenv/bin/activate
76+
```
77+
```
78+
pip install -r requirements.txt
79+
```
80+
81+
## Run Your Application
82+
83+
### Option 1 : With Host Agent
84+
To run your application, use the following command:
85+
```shell
86+
middleware-apm run python app.py
87+
```
88+
### Option 2 : Serverless Setup
89+
```shell
90+
MW_API_KEY=********** MW_TARGET=https://*****.middleware.io:443 middleware-apm run python app.py
91+
```
92+
#### Note: If `middleware.ini` isn't in your project's root, set `MIDDLEWARE_CONFIG_FILE=./path/to/middleware.ini` along with the above run command.
93+
94+
---------------------------------
95+
96+
## Troubleshooting Demo
97+
If you face any protoc specific errors, Try setting ...
98+
```
99+
export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
100+
```
101+
---------------------------------
102+
## Run on Docker
103+
1. Build: `docker build -t demo-python .`
104+
2. Run: `docker run demo-python`
105+
3. Debug: `docker run -it demo-python sh`

basic/app.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
from flask import Flask, jsonify, request
2+
import traceback
3+
import logging
4+
from functools import wraps
5+
from opentelemetry import trace, metrics
6+
from opentelemetry.trace.status import StatusCode
7+
8+
9+
from middleware import MwTracker
10+
tracker=MwTracker()
11+
12+
# Initialize Flask app
13+
app = Flask(__name__)
14+
15+
# Trace provider
16+
tracer = trace.get_tracer("custom-tracer")
17+
18+
# Metric provider
19+
meter = metrics.get_meter("custom-meter")
20+
21+
# Setup Python logging
22+
logging.basicConfig(level=logging.INFO)
23+
logger = logging.getLogger(__name__)
24+
25+
# Custom Metrics: Counter
26+
request_counter = meter.create_counter(
27+
name="requests_total",
28+
description="Total number of requests",
29+
unit="1"
30+
)
31+
32+
# Custom Traces: Tracing decorator with error handling
33+
def trace_request(func):
34+
@wraps(func) # Preserve the original function's name and docstring
35+
def wrapper(*args, **kwargs):
36+
with tracer.start_as_current_span(func.__name__) as span:
37+
span.set_attribute("custom.attribute", "example_value")
38+
39+
# Log the trace and span IDs
40+
span_context = span.get_span_context()
41+
logger.info(f"Start processing {func.__name__}: trace_id={span_context.trace_id}, span_id={span_context.span_id}")
42+
43+
try:
44+
result = func(*args, **kwargs)
45+
46+
# Only check status code for POST request handling
47+
if request.method == 'POST':
48+
if result.status_code >= 400:
49+
span.set_status(StatusCode.ERROR)
50+
logger.error(f"Error occurred in {func.__name__}: status_code={result.status_code}")
51+
52+
return result
53+
54+
except Exception as e:
55+
span.set_status(StatusCode.ERROR)
56+
span.record_exception(e)
57+
logger.error(f"Exception occurred in {func.__name__}: {str(e)}")
58+
logger.error(f"Stack Trace:\n{traceback.format_exc()}") # Log the full stack trace
59+
return jsonify({"error": "An internal error occurred"}), 500
60+
return wrapper
61+
62+
@app.route('/')
63+
@trace_request
64+
def home():
65+
request_counter.add(1, {"endpoint": "home"})
66+
logger.info("Home endpoint accessed")
67+
return jsonify({"message": "Welcome to the Flask app!"})
68+
69+
@app.route('/process', methods=['GET', 'POST'])
70+
@trace_request
71+
def process_data():
72+
if request.method == 'GET':
73+
# Render a simple HTML form for demonstration
74+
return '''
75+
<form method="POST">
76+
<label for="data">Enter some data (JSON format):</label><br>
77+
<textarea id="data" name="data" rows="4" cols="50"></textarea><br>
78+
<input type="submit" value="Submit">
79+
</form>
80+
'''
81+
82+
if request.method == 'POST':
83+
# Process JSON data submitted via the form
84+
try:
85+
data = request.json if request.is_json else request.form.get('data')
86+
request_counter.add(1, {"endpoint": "process"})
87+
logger.info(f"Processing data: {data}")
88+
89+
with tracer.start_as_current_span("data_processing") as span:
90+
span.set_attribute("request.data", str(data))
91+
# Simulate processing
92+
processed_data = {"processed": data}
93+
logger.info("Data processed successfully")
94+
95+
response = jsonify(processed_data) # Create response object
96+
97+
return response # Return the response object
98+
99+
except Exception as e:
100+
span.set_status(StatusCode.ERROR)
101+
span.record_exception(e)
102+
logger.error(f"Exception occurred in process_data: {str(e)}")
103+
logger.error(f"Stack Trace:\n{traceback.format_exc()}")
104+
return jsonify({"error": "An internal error occurred"}), 500
105+
106+
@app.route('/error')
107+
@trace_request
108+
def error():
109+
request_counter.add(1, {"endpoint": "error"})
110+
logger.warning("Error endpoint accessed, simulating an error")
111+
112+
# Simulate an exception to trigger stack trace logging
113+
raise ValueError("Simulated internal server error")
114+
115+
if __name__ == '__main__':
116+
app.run(port=5000)

0 commit comments

Comments
 (0)