Skip to content

Commit

Permalink
python instrument demo
Browse files Browse the repository at this point in the history
  • Loading branch information
arjun-mw committed Sep 25, 2024
1 parent 6ad6136 commit 9c25f96
Show file tree
Hide file tree
Showing 174 changed files with 34,735 additions and 0 deletions.
66 changes: 66 additions & 0 deletions aws-lambda/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import time
import requests
import asyncio
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor, ConsoleSpanExporter
from opentelemetry.instrumentation.aws_lambda import AwsLambdaInstrumentor

# Initialize OpenTelemetry tracing
provider = TracerProvider()
processor = SimpleSpanProcessor(ConsoleSpanExporter()) # Exports to console for debugging
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)

# Instrument the tracer provider with OpenTelemetry for Lambda
AwsLambdaInstrumentor().instrument_tracer_provider()

# Get the OpenTelemetry tracer
tracer = trace.get_tracer(__name__)

# Function to make an HTTP GET request
def get_request():
url = "https://opentelemetry.io/"
try:
start_time = time.time()
response = requests.get(url, timeout=(2, 5)) # (connection timeout, read timeout)
duration = time.time() - start_time
print(f"Request completed in {duration:.2f} seconds")
return response.status_code
except requests.Timeout:
raise Exception("Request timed out")
except requests.RequestException as err:
raise Exception(f"HTTP Request failed: {err}")

# Lambda handler function
def lambda_handler(event, context):
try:
# Start a tracing span for the Lambda function execution
with tracer.start_as_current_span("LambdaFunctionExecution"):
# Log the start time of the Lambda execution
start_time = time.time()

# Make the external HTTP request
result = get_request()

# Log the total execution time
print(f"Total execution time: {time.time() - start_time:.2f} seconds")

# Add a custom event and attributes to the current span
current_span = trace.get_current_span()
current_span.add_event("CustomEvent", {"description": "This is a custom event"})
current_span.set_attribute("http.status_code", result)

# Return the successful response
return {
"statusCode": 200,
"body": f"Request completed with status code {result}"
}

# Handle any exceptions and return a 400 response
except Exception as error:
return {
"statusCode": 400,
"body": str(error)
}

105 changes: 105 additions & 0 deletions basic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Python APM Guide
You can follow our [documentation](https://docs.middleware.io/docs/apm-configuration/python/python-apm-setup) to setup APM for your Python application.

[![PyPI - Version](https://img.shields.io/pypi/v/middleware-apm)](https://pypi.org/project/middleware-apm/)


| Traces | Metrics | Profiling | Logs (App/Custom) |
|:--------:|:---------:|:-----------:|:-------------------:|
| Yes | Yes | Yes | Yes/Yes |

## Prerequisites
Ensure that you have the Middleware Host Agent installed to view Python demo data on your dashboard.

---------------------

## Installing the Package
Run the following commands in your terminal:
### Step 1: Install Middleware APM package
```shell
pip install middleware-apm
```

## Your Sample Code
By using all the MW's APM functionalities like: Distributed-tracing, Logs, Metrics and Profiling, your code will look like this:
```python
import logging

from middleware import MwTracker
tracker=MwTracker()

logging.warning("Sample Warning Log.")
logging.error("Sample Error Log.", extra={'tester': 'Alex'})
```
## Setup middleware.ini File
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.
```ini
# ---------------------------------------------------------------------------
# This file contains settings for the Middleware Python-APM Agent.
# Here are the settings that are common to all environments.
# ---------------------------------------------------------------------------

[middleware.common]

# The name of your application as service-name, as it will appear in the UI to filter out your data.
service_name = Python-APM-Service

# This Token binds the Python Agent's data and profiling data to your account.
access_token = {YOUR-ACCESS-TOKEN}

# The service name, where Middleware Agent is running, in case of K8s.
;mw_agent_service = mw-service.mw-agent-ns.svc.cluster.local

# Toggle to enable/disable distributed traces for your application.
collect_traces = true

# Toggle to enable/disable the collection of metrics for your application.
collect_metrics = true

# Toggle to enable/disable the collection of logs for your application.
collect_logs = true

# Toggle to enable/disable the collection of profiling data for your application.
collect_profiling = true

# ---------------------------------------------------------------------------
```
#### Note: You need to replace <strong>\{YOUR-ACCESS-TOKEN\}</strong> with your APM's Access Token.


## Setup Virtual Environment
```
python -m venv newenv
```
```
source newenv/bin/activate
```
```
pip install -r requirements.txt
```

## Run Your Application

### Option 1 : With Host Agent
To run your application, use the following command:
```shell
middleware-apm run python app.py
```
### Option 2 : Serverless Setup
```shell
MW_API_KEY=********** MW_TARGET=https://*****.middleware.io:443 middleware-apm run python app.py
```
#### 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.

---------------------------------

## Troubleshooting Demo
If you face any protoc specific errors, Try setting ...
```
export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
```
---------------------------------
## Run on Docker
1. Build: `docker build -t demo-python .`
2. Run: `docker run demo-python`
3. Debug: `docker run -it demo-python sh`
116 changes: 116 additions & 0 deletions basic/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from flask import Flask, jsonify, request
import traceback
import logging
from functools import wraps
from opentelemetry import trace, metrics
from opentelemetry.trace.status import StatusCode


from middleware import MwTracker
tracker=MwTracker()

# Initialize Flask app
app = Flask(__name__)

# Trace provider
tracer = trace.get_tracer("custom-tracer")

# Metric provider
meter = metrics.get_meter("custom-meter")

# Setup Python logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Custom Metrics: Counter
request_counter = meter.create_counter(
name="requests_total",
description="Total number of requests",
unit="1"
)

# Custom Traces: Tracing decorator with error handling
def trace_request(func):
@wraps(func) # Preserve the original function's name and docstring
def wrapper(*args, **kwargs):
with tracer.start_as_current_span(func.__name__) as span:
span.set_attribute("custom.attribute", "example_value")

# Log the trace and span IDs
span_context = span.get_span_context()
logger.info(f"Start processing {func.__name__}: trace_id={span_context.trace_id}, span_id={span_context.span_id}")

try:
result = func(*args, **kwargs)

# Only check status code for POST request handling
if request.method == 'POST':
if result.status_code >= 400:
span.set_status(StatusCode.ERROR)
logger.error(f"Error occurred in {func.__name__}: status_code={result.status_code}")

return result

except Exception as e:
span.set_status(StatusCode.ERROR)
span.record_exception(e)
logger.error(f"Exception occurred in {func.__name__}: {str(e)}")
logger.error(f"Stack Trace:\n{traceback.format_exc()}") # Log the full stack trace
return jsonify({"error": "An internal error occurred"}), 500
return wrapper

@app.route('/')
@trace_request
def home():
request_counter.add(1, {"endpoint": "home"})
logger.info("Home endpoint accessed")
return jsonify({"message": "Welcome to the Flask app!"})

@app.route('/process', methods=['GET', 'POST'])
@trace_request
def process_data():
if request.method == 'GET':
# Render a simple HTML form for demonstration
return '''
<form method="POST">
<label for="data">Enter some data (JSON format):</label><br>
<textarea id="data" name="data" rows="4" cols="50"></textarea><br>
<input type="submit" value="Submit">
</form>
'''

if request.method == 'POST':
# Process JSON data submitted via the form
try:
data = request.json if request.is_json else request.form.get('data')
request_counter.add(1, {"endpoint": "process"})
logger.info(f"Processing data: {data}")

with tracer.start_as_current_span("data_processing") as span:
span.set_attribute("request.data", str(data))
# Simulate processing
processed_data = {"processed": data}
logger.info("Data processed successfully")

response = jsonify(processed_data) # Create response object

return response # Return the response object

except Exception as e:
span.set_status(StatusCode.ERROR)
span.record_exception(e)
logger.error(f"Exception occurred in process_data: {str(e)}")
logger.error(f"Stack Trace:\n{traceback.format_exc()}")
return jsonify({"error": "An internal error occurred"}), 500

@app.route('/error')
@trace_request
def error():
request_counter.add(1, {"endpoint": "error"})
logger.warning("Error endpoint accessed, simulating an error")

# Simulate an exception to trigger stack trace logging
raise ValueError("Simulated internal server error")

if __name__ == '__main__':
app.run(port=5000)
Loading

0 comments on commit 9c25f96

Please sign in to comment.