Introduction
Application Performance Monitoring (APM) is critical for understanding how your application performs in production. Oracle Cloud Infrastructure (OCI) APM provides comprehensive monitoring for both backend services and frontend user experiences.
In this guide, I'll walk you through setting up APM for a Flask application running on OCI, covering both:
- Backend tracingusing OpenTelemetry
- Frontend monitoringusing Real User Monitoring (RUM)
This isn't a theoretical guide - these are the exact steps I used to instrument a production eCommerce application.
Table of Contents
Prerequisites
Before starting, you'll need:
- OCI Accountwith admin access (or IAM permissions to create policies)
-compute instancerunning your Flask application -
Flask Application(any version, this guide uses Flask 3.0.0)
-
Python 3.8+installed
-
Basic familiaritywith OCI Console and command lineApplication Stack(for reference):
-
Flask 3.0.0
- **Gunicorn WSGI server
-
Oracle Autonomous Database (ATP)
-
Running on OCIcompute instance---
Part 1: Create APM Domain
AnAPM domain is the container for all your monitoring data.
Step 1.1: Navigate to APM Service
-
Log in toOCI Console
-
Open the hamburger menu (≡)
-
Go to:Observability & Management→Application Performance Monitoring→Administration### Step 1.2: Create APM Domain
-
Click Create APM Domain
-
Fill in the details:
-Name:Production-App-Monitoring(choose a meaningful name)
-Compartment: Selectyour compartment-Description: "APM for TechHub eCommerce Application" -
Click CreateWait time**: 2-3 minutes for the domainto become active.
Step 1.3: Gather APM Credentials
Once the domain is active, you need three pieces of information:
- APM
Domain OCID
- Click on yourAPM domain- Copy the OCID(looks like:
ocid1.apmdomain.oc1.iad.amaaa...)
- Data Upload Endpoint- In the APM domain details page
- Look for "Data Upload Endpoint"
- Copy the full URL (e.g.,
https://exampleabc123xyz...apm-agt.us-ashburn-1.oci.oraclecloud.com)
- Private Data Key(for backend tracing)
- Click Data Keys in the left menu
- Under "Private Data Keys", click**Generate Private Data Key- Name it:
backend-tracing-key - Copy the key(e.g.,
EXAMPLE1A2B3C4D5E6F7G8H9I0J1K2L3M4N5)
-Important:**Save this immediately - you can't retrieve it later!
- Public Data Key(for RUM)
- Same "Data Keys" page
- Under "Public Data Keys", click**Generate Public Data Key- Name it:
rum-frontend-key - Copy the key(e.g.,
EXAMPLE9Z8Y7X6W5V4U3T2S1R0Q9P8O7N6M5)Pro Tip:**Save these in a temporary text file - you'll need them in the next steps.
Part 2: Configure IAM Permissions
Yourcompute instanceneeds permission to send data to APM.
Step 2.1: Create Dynamic Group
Dynamic groups allow you to grant permissions tocompute instances without managing individual credentials.
-
Go to:Identity & Security→Identity→Dynamic Groups
-
Click Create Dynamic Group
-
Fill in:
-Name:techhub-compute-dg
-**Description: "compute instances for TechHub application" -
Add matching rule:
ALL {instance.compartment.id = 'ocid1.compartment.oc1..examplecompartment123'}
Replace ocid1.compartment.oc1..examplecompartment123 with your compartment OCID.
How to get your compartment OCID:
# On your compute instance
oci iam compartment list --all --compartment-id-in-subtree true | grep -A 2 "name.*YourCompartmentName"
Step 2.2: Create IAM Policy
-
Go to: Identity & Security → Identity → Policies
-
Click Create Policy
-
Fill in:
- Name:techhub-compute-policy
- Description: "Allow compute instances to use OCI services" -
Add policy statements:
Allow dynamic-group techhub-compute-dg to manage apm-domains in compartment id ocid1.compartment.oc1..examplecompartment123
Allow dynamic-group techhub-compute-dg to read metrics in compartment id ocid1.compartment.oc1..examplecompartment123
Allow dynamic-group techhub-compute-dg to use log-content in compartment id ocid1.compartment.oc1..examplecompartment123
Important:Use
manage apm-domains, not justuse apm-domains. Themanagepermission is required for OTLP trace ingestion.
Step 2.3: Verify Instance Principal
On your compute instance, verify authentication works:
# This should return your instance metadata without errors
oci os ns get --auth instance_principal
If you get an error about authentication, your dynamic group matching rule may be incorrect.
Part 3: Set Up Backend Tracing (OpenTelemetry)
Now we'll instrument your Flask application to send traces to APM.
Step 3.1: Install OpenTelemetry Packages
SSH into your compute instanceand install the required packages:
cd /path/to/your/flask/app
source venv/bin/activate # Activate your virtual environment
pip install opentelemetry-api \
opentelemetry-sdk \
opentelemetry-instrumentation-flask \
opentelemetry-exporter-otlp-proto-http
Package purposes: - opentelemetry-api: Core OpenTelemetry API
- opentelemetry-sdk: Implementation of the API
- opentelemetry-instrumentation-flask: Automatic Flask instrumentation
- opentelemetry-exporter-otlp-proto-http: Export traces via OTLP/HTTP
Step 3.2: Configure Environment Variables
Add APM configuration to your .env file (or environment):
# OCI APM Configuration
OCI_APM_DOMAIN_ID=ocid1.apmdomain.oc1.iad.exampledomainid12345...
OCI_APM_DATA_UPLOAD_ENDPOINT=https://exampleabc123xyz...apm-agt.us-ashburn-1.oci.oraclecloud.com
OCI_APM_PRIVATE_KEY=EXAMPLE1A2B3C4D5E6F7G8H9I0J1K2L3M4N5
OCI_APM_RUM_PUBLIC_KEY=EXAMPLE9Z8Y7X6W5V4U3T2S1R0Q9P8O7N6M5
# Application Identification
APP_NAME=TechHub-Electronics-eCommerce
```**Security Note:**Never commit `.env` to version control. Add it to `.gitignore`.
### Step 3.3: Load Configuration
In your**`config/config.py`**(or wherever you configure Flask):
```python
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
# ... your existing config ...
# OCI APM Configuration
OCI_APM_DOMAIN_ID = os.environ.get('OCI_APM_DOMAIN_ID', '')
OCI_APM_DATA_UPLOAD_ENDPOINT = os.environ.get('OCI_APM_DATA_UPLOAD_ENDPOINT', '')
OCI_APM_PRIVATE_KEY = os.environ.get('OCI_APM_PRIVATE_KEY', '')
OCI_APM_RUM_PUBLIC_KEY = os.environ.get('OCI_APM_RUM_PUBLIC_KEY', '')
APP_NAME = os.environ.get('APP_NAME', 'Flask-Application')
Step 3.4: Initialize OpenTelemetry
In your application factory (app/__init__.pyor create_app()):
from flask import Flask
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.instrumentation.flask import FlaskInstrumentor
import logging
def create_app():
app = Flask(__name__)
app.config.from_object('config.config.Config')
# Set up APM tracing
setup_apm(app)
# ... rest of your app initialization ...
return app
def setup_apm(app):
"""Configure OpenTelemetry tracing for OCI APM"""
# Get configuration
apm_endpoint = app.config.get('OCI_APM_DATA_UPLOAD_ENDPOINT')
apm_private_key = app.config.get('OCI_APM_PRIVATE_KEY')
service_name = app.config.get('APP_NAME', 'Flask-Application')
# Create tracer provider
provider = TracerProvider(
resource=Resource.create({
"service.name": service_name,
"service.version": "1.0.0",
"deployment.environment": "production"
})
)
# Configure OTLP exporter if APM is configured
if apm_endpoint and apm_private_key:
try:
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
# CRITICAL: Constructcorrect endpointURL
# Format: https://<endpoint>/20200101/opentelemetry/private/v1/traces
otlp_endpoint = f"{apm_endpoint}/20200101/opentelemetry/private/v1/traces"
# CRITICAL: Use correct authentication header format
# Format: "authorization": "dataKey <private-data-key>"
otlp_exporter = OTLPSpanExporter(
endpoint=otlp_endpoint,
headers={"authorization": f"dataKey {apm_private_key}"}
)
# Use BatchSpanProcessor for better performance
processor = BatchSpanProcessor(otlp_exporter)
provider.add_span_processor(processor)
app.logger.info(f"APM tracing configured for OCI APM: {otlp_endpoint}")
except Exception as e:
app.logger.error(f"Failed to configure APM tracing: {str(e)}")
else:
app.logger.warning("APM configuration incomplete - tracing disabled")
# Set the global tracer provider
trace.set_tracer_provider(provider)
# Instrument Flask automatically
FlaskInstrumentor().instrument_app(app)
app.logger.info("APM tracing enabled")
Key Points: 1.Endpoint URL: Must include /20200101/opentelemetry/private/v1/traces path
-
Authentication: Use
"authorization": "dataKey <key>"format (NOT "Bearer") -
BatchSpanProcessor: Batches spans for better performance
-
Resource attributes: Helps identify your service in APM
Step 3.5: Add Database Instrumentation
If you're using a database (you should be!), add custom spans for database operations.
In your database module (e.g.,config/database.py):
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
class Database:
def execute_query(self, query, params=None, fetch_one=False, fetch_all=True, commit=False):
"""Execute a query with APM tracing"""
# Extract operation type (SELECT, INSERT, UPDATE, DELETE)
operation = query.strip().split()[0].upper() if query else "UNKNOWN"
# Create a span for this database operation
with tracer.start_as_current_span(
f"db.{operation.lower()}",
attributes={
"db.system": "oracle", # or "postgresql", "mysql", etc.
"db.name": self.database_name,
"db.user": self.user,
"db.statement": query[:500], # Limit to 500 chars
"db.operation": operation
}
) as span:
try:
with self.get_connection() as conn:
cursor = conn.cursor()
# Execute query
if params:
cursor.execute(query, params)
span.set_attribute("db.params_count", len(params))
else:
cursor.execute(query)
# Handle commits
if commit:
conn.commit()
rowcount = cursor.rowcount
span.set_attribute("db.rows_affected", rowcount)
return rowcount
# Handle fetches
if fetch_one:
result = cursor.fetchone()
span.set_attribute("db.rows_returned", 1 if result else 0)
return result
elif fetch_all:
rows = cursor.fetchall()
span.set_attribute("db.rows_returned", len(rows))
return rows
except Exception as e:
# Record error in span
span.set_attribute("error", True)
span.set_attribute("error.type", type(e).__name__)
span.set_attribute("error.message", str(e))
raise
finally:
cursor.close()
```Benefits:
- See slow queries in APM
- Track database errors
- Understand query patterns
- Identify N+1 query problems
### Step 3.6: Add Business Logic Instrumentation
Instrument important business operations. Example for order creation:
```python
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
class Order:
@staticmethod
def create(customer_id, items, shipping_info, payment_method):
"""Create a new order with APM tracing"""
# Create span for order creation
with tracer.start_as_current_span(
"order.create",
attributes={
"order.customer_id": customer_id,
"order.items_count": len(items),
"order.payment_method": payment_method,
"order.shipping_state": shipping_info.get('state', 'unknown')
}
) as span:
# Calculate total
total = sum(item['quantity'] * item['unit_price'] for item in items)
span.set_attribute("order.total_amount", float(total))
# Insert order into database
db = get_db()
order_id = db.execute_query(
"INSERT INTO orders (...) VALUES (...) RETURNING order_id",
params={...},
commit=True
)
span.set_attribute("order.id", order_id)
# Add child span for order items
with tracer.start_as_current_span("order.add_items") as items_span:
for item in items:
db.execute_query(
"INSERT INTO order_items (...) VALUES (...)",
params={...},
commit=True
)
items_span.set_attribute("items.inserted", len(items))
return order_id
What to instrument: - Order creation/processing
- Payment processing
- Inventory updates
-
User authentication
-
API calls to external services
-
File uploads/processing
-
Email sending
Step 3.7: Restart Your Application
# If using Gunicorn with a PID file
kill -HUP $(cat gunicorn.pid)
# Or restart the service
sudo systemctl restart your-app-service
# Or kill and restart manually
pkill -f gunicorn
gunicorn -c config/gunicorn.conf.py --bind 0.0.0.0:80 run:app
Step 3.8: Verify Backend Tracing
Check your application logs for confirmation:
tail -f /path/to/your/app/logs/app.log | grep -i apm
Expected output: ```
APM tracing configured for OCI APM: https://...apm-agt.us-ashburn-1.oci.oraclecloud.com/20200101/opentelemetry/private/v1/traces
APM tracing enabled
```No errors about export failures = Success! ✅
Part 4: Set Up Frontend Monitoring (RUM)
Real User Monitoring captures actual user experience data in the browser.
Step 4.1: Configure RUM in HTML Template
In your base HTML template (e.g., templates/base.html), add this in the <head> section:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}Your App{% endblock %}</title>
<!-- Your existing head content -->
<!-- OCI APM Real User Monitoring (RUM) -->
{% if config.get('OCI_APM_DOMAIN_ID') and config.get('OCI_APM_RUM_PUBLIC_KEY') %}
<script>
window.apmrum = (window.apmrum || {});
window.apmrum.serviceName = '{{ config.get("APP_NAME", "Flask-Application") }}';
window.apmrum.webApplication = 'web-frontend';
window.apmrum.ociDataUploadEndpoint = '{{ config.get("OCI_APM_DATA_UPLOAD_ENDPOINT", "") }}';
window.apmrum.OracleAPMPublicDataKey = '{{ config.get("OCI_APM_RUM_PUBLIC_KEY") }}';
</script>
<script async crossorigin="anonymous"
src="{{ config.get('OCI_APM_DATA_UPLOAD_ENDPOINT', '') }}/static/jslib/apmrum.min.js"></script>
{% endif %}
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
Critical Details: 1.Correct filename: apmrum.min.js (NOT oracleapmapm.js)
-
Correct path:
{endpoint}/static/jslib/apmrum.min.js
3.public key: Use the publicdata key(not private!) -
Conditional loading: Only loads if APM is configured
Step 4.2: Restart Application
kill -HUP $(cat gunicorn.pid)
Step 4.3: Verify RUM in Browser
-
Open your application in a browser
-
Open DevTools (F12)
-
Go toConsoletab
-
Type:
window.apmrum
Expected output: ```javascript
{
serviceName: "TechHub-Electronics-eCommerce",
webApplication: "web-frontend",
ociDataUploadEndpoint: "https://exampleabc123xyz...apm-agt.us-ashburn-1.oci.oraclecloud.com",
OracleAPMPublicDataKey: "EXAMPLE9Z8Y7X6W5V4U3T2S1R0Q9P8O7N6M5",
// ... other properties
}
### Step 4.4: Verify RUM SDK Loaded
In DevToolsNetworktab:
1. Refresh the page
2. Filter by: `apmrum` or `jslib`
3. Look for: `apmrum.min.js`
4. **Status should be**: 200 OK (not 404)
### Step 4.5: Verify RUM Sending Data
In DevToolsNetworktab:
1. Filter by: `apm-agt` or `public-span`
2. Browse your site (click links, add items to cart, etc.)
3. Look for:POSTrequests to `*.apm-agt.*.oci.oraclecloud.com`
4. Path should be: `/20200101/observations/public-span`
5. **Status should be**: 200 OK**If you see these POST requests with 200 OK = RUM is working! ✅**
## Part 5: Verification and Testing
### Step 5.1: Generate Test Traffic
Create realistic traffic to generate traces:
```bash
# Simple traffic generation script
for i in {1..10}; do
echo "Request $i"
curl -s http://your-app.com/ > /dev/null
curl -s http://your-app.com/products > /dev/null
curl -s http://your-app.com/api/products/1 > /dev/null
sleep 2
done
Or browse manually:
- View homepage
-
Browse products
-
View product details
-
Add items to cart
-
Simulate checkout
- Search for productsPro Tip:Do something distinctive (like creating an order with a specific item) so you can easily find it in APM.
Step 5.2: Check Backend Traces in OCI ConsoleWait 5-15 minutesfor traces to appear, then:
-
Go to:OCI Console→Observability & Management→Application Performance Monitoring
-
Select yourAPM domain:Production-App-Monitoring
-
Click:Trace Explorerin the left menu
-
Set time range:Last 2 Hours
-
Remove all filters (or filter by your service name)
-
Click:Run QueryWhat you should see**:
- HTTP requests (GET /products, POST /api/checkout, etc.)
-
Database queries nested under HTTP requests
-
Business operations (order.create, inventory.update_stock, etc.)
- Response times and status codesTrace hierarchy example:
GET /api/checkout
├─ order.create
│ ├─ db.insert (orders table)
│ ├─ order.add_items
│ │ ├─ db.insert (order_items)
│ │ └─ inventory.update_stock
│ │ └─ db.update (products)
│ └─ db.select (verify order)
└─ Response: 200 OK (234ms)
Step 5.3: Check RUM Data in OCI ConsoleWait 10-15 minutesafter browsing, then:
-
Same location:Trace Explorer
-
Add filter:Trace Type = Browser
-
Time range:Last 1 Hour
-
Click:Run QueryWhat you should see**:
- Page load events
-
User interactions (clicks, form submissions)
-
AJAX/Fetch requests
-
JavaScript errors (if any)
-
Browser metrics (load time, DOM interactive, etc.)
Step 5.4: Explore APM Dashboards
Check out the built-in dashboards:Application Server Dashboard:
- Go to:Applications→Servers→ Select your service- Shows: Request rates, response times, error rates, throughputDatabase Dashboard:
- Go to:Applications→Databases- Shows: Query performance, slow queries, database connectionsRUM Dashboard:
- Go to:Browser Sessions**- Shows: Page load times, user sessions, geographic distribution, browser types
Troubleshooting
Backend Tracing Issues
Problem: No traces appearing in OCI Console**Check application logs
**:
tail -100 /path/to/app/logs/app.log | grep -i -E "(apm|error|export)"
Common issues: 1.404 "NotAuthorizedOrNotFound"errors in logs
-Cause:Wrong endpointURL or authentication
-Fix:Verify endpointincludes /private/v1/traces path
-Fix: Verify using "dataKey <key>" format, not "Bearer"
2.401 Unauthorizederrors
-Cause: Invalid privatedata key-Fix: Regenerate privatedata keyin APM console
-Fix: Update .env file withnew key
3.403 Forbiddenerrors
-Cause: IAM policy insufficient
-Fix: Change policy from use to manage apm-domains
-Fix: Verify dynamic group matching rule includesyour instance4.No errors but no traces-Cause: Traces not exported yet (batching)
-Fix: Wait 5-15 minutes
-Fix: Generate more traffic to trigger batch export
Problem: Traces appear but missing database spans**Check
**:
- Is database instrumentation code added?
-
Are you using the instrumented
execute_query()method? -
Check for errors in logs when executing queriesDebug:
# Add temporary logging
with tracer.start_as_current_span("db.select") as span:
print(f"Span ID: {span.get_span_context().span_id}") # Should print a number
# ... your database code
RUM Issues
Problem: RUM SDK returns 404Check the URL in browser DevTools Network tab.**Common mistakes
**:
- Wrong filename: Using
oracleapmapm.jsinstead ofapmrum.min.js
```html
```
- Wrong CDN: Using generic Oracle CDN instead ofAPM endpoint-Wrong:
https://static.oracle.com/cdn/...
-Correct:https://<your-apm-endpoint>/static/jslib/apmrum.min.js
Problem: window.apmrum is undefined**Possible causes
**:
-
RUM SDK didn't load (check Network tab for 404)
-
JavaScript error preventing initialization
-
Ad blocker blocking the scriptDebug:
// In browser console
console.log(document.querySelector('script[src*="apmrum"]')); // Should show the script tag
```Fix:
- Try incognito mode (disables extensions)
- Check browser console for JavaScript errors
- Verify script tag is actually in HTML: View Page Source
#### Problem: RUM SDK loads but no beacons sent**Check browser console for CORS errors**.
**Possible causes:
1. Wrong publicdata key**
2.**Wrong endpoint**URL in configuration
3. Network/firewall blockingOCI endpoints**Debug**:
```javascript
// In browser console
console.log(window.apmrum.ociDataUploadEndpoint); // Should show full URL
console.log(window.apmrum.OracleAPMPublicDataKey); // Should show your key
```**Test manually:
```javascript
// Try to send a test beacon
fetch(window.apmrum.ociDataUploadEndpoint + '/20200101/observations/public-span', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({test: 'data'})
})
.then(r => console.log('Status:', r.status))
.catch(e => console.error('Error:', e));
Expected: Status 400 (bad request databut endpoint**is reachable)
Not expected: CORS error or network error
General Debugging
Enable verbose
**OpenTelemetry logging
In your Flask app initialization:
import logging
# Enable OpenTelemetry debug logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("opentelemetry").setLevel(logging.DEBUG)
This will show detailed export information.
Verify IAM authentication
On your compute instance:
#Test instanceprincipal auth
oci os ns get --auth instance_principal
# If this works, instance principal is configured correctly
Check network connectivity
# Test connectivity toAPM endpointcurl -I https://exampleabc123xyz...apm-agt.us-ashburn-1.oci.oraclecloud.com/static/jslib/apmrum.min.js
# Expected: HTTP/1.1 200 OK
Best Practices
1. Resource Attributes
Always set meaningful resource attributes:
from opentelemetry.sdk.resources import Resource
resource = Resource.create({
"service.name": "techhub-ecommerce",
"service.version": "2.1.0",
"service.instance.id": os.getenv("HOSTNAME", "unknown"),
"deployment.environment": os.getenv("ENV", "production"),
"cloud.provider": "oci",
"cloud.region": "us-ashburn-1"
})
```Benefits:
- Filter traces by service version
- Identifywhich instancegenerated a trace
- Separate production vs staging traces
### 2. Span Attributes
Add contextual attributes to spans:
```python
with tracer.start_as_current_span("process_payment") as span:
span.set_attribute("payment.method", payment_method)
span.set_attribute("payment.amount", amount)
span.set_attribute("customer.id", customer_id)
span.set_attribute("order.id", order_id)
# ... process payment ...
```Benefits:
- Search traces bycustomer ID**- Find all failed payments
- Analyze payment methods
### 3. Error Recording
Always record errors in spans:
```python
try:
result = risky_operation()
except Exception as e:
span.set_attribute("error", True)
span.set_attribute("error.type", type(e).__name__)
span.set_attribute("error.message", str(e))
span.record_exception(e) # Records full stack trace
raise
4. Sampling (for high-traffic apps)
If you have very high traffic, implement sampling:
from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
# Sample 10% of traces
sampler = TraceIdRatioBased(0.1)
provider = TracerProvider(
sampler=sampler,
resource=resource
)
When to use: - More than 1000 requests/minute
- APM costs becoming significant
- Only need representative sample
5. Sensitive Data
Never log sensitive data in spans:
# BAD
span.set_attribute("user.password", password) # Never!
span.set_attribute("credit.card.number", cc_number) # Never!
# GOOD
span.set_attribute("user.id", user_id) # OK
span.set_attribute("payment.last4", cc_last4) # OK
6. Span Naming
Use consistent, descriptive span names:
# Good naming
"db.select"
"http.request"
"order.create"
"email.send"
"cache.get"
# Bad naming
"function1"
"do_stuff"
"handler"
7. BatchSpanProcessor Configuration
For production, tune the batch processor:
from opentelemetry.sdk.trace.export import BatchSpanProcessor
processor = BatchSpanProcessor(
otlp_exporter,
max_queue_size=2048, # Default: 2048
schedule_delay_millis=5000, # Export every 5 seconds
max_export_batch_size=512, # Default: 512
export_timeout_millis=30000 # 30 second timeout
)
Tuning guidelines: - High traffic: Increase max_queue_size and max_export_batch_size
- Low latency needed: Decrease schedule_delay_millis
- Slow network: Increase
export_timeout_millis
8. Monitoring the Monitor
Add health checks for APM export:
from opentelemetry.sdk.trace.export import ConsoleSpanExporter
# In development, also export to console
if app.config['DEBUG']:
console_exporter = ConsoleSpanExporter()
provider.add_span_processor(BatchSpanProcessor(console_exporter))
9. RUM Performance
Minimize RUM impact on page load:
<!-- Use async and defer -->
<script async defer crossorigin="anonymous"
src="{{ config.get('OCI_APM_DATA_UPLOAD_ENDPOINT') }}/static/jslib/apmrum.min.js"></script>
10. Cost Optimization
Monitor your APM usage:
- Go to:OCI Console→Cost Management- Filter by:Application Performance Monitoring- Set up budget alertsCost factors:
-
Number of spans ingested
-
Data retention period
-
Number of synthetic monitors (if using)Optimization strategies:
-
Use sampling for high-traffic applications
-
Reduce span attributes (keep only essential ones)
-
Shorter retention for non-production environments
Conclusion
You now have comprehensive APM monitoring for your Flask application:Backend Monitoring:
- ✅ HTTP request tracing
- ✅ Database query performance
- ✅ Business logic instrumentation
- ✅ Error trackingFrontend Monitoring:
- ✅ Real user page load times
- ✅ User interaction tracking
- ✅ AJAX call monitoring
- ✅ Browser error trackingWhat's Next?
1.Set up alerts: Configure notifications for slow requests or errors
-
Create custom dashboards: Visualize your KPIs
-
Synthetic monitoring: Add uptime checks
-
Distributed tracing: If you have microservices, trace across services
-
Log correlation: Link logs to traces usingtrace IDs
Additional Resources
About This Guide
This guide is based on real-world implementation of APM for a Flask eCommerce application running on OCI. All steps, code examples, and troubleshooting tips come from actual experience setting up production monitoring.Application Details:
- Flask 3.0.0
- Oracle Autonomous Database (ATP)
- **Gunicorn WSGI server
- OCIcompute instance(Oracle Linux 9)
- ~500 requests/hour production trafficTime to implement: 2-3 hours for full setup (backend + frontend)
---Questions or issues?Check the Troubleshooting section or consult the official OCI APM documentation.Happy monitoring!📊🚀