-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
174 changed files
with
34,735 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Oops, something went wrong.