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

  1. Prerequisites

  2. Part 1: Create APM Domain

  3. Part 2: Configure IAM Permissions

  4. Part 3: Set Up Backend Tracing (OpenTelemetry)

  5. Part 4: Set Up Frontend Monitoring (RUM)

  6. Part 5: Verification and Testing

  7. Troubleshooting

  8. Best Practices

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

  1. Log in toOCI Console

  2. Open the hamburger menu (≡)

  3. Go to:Observability & ManagementApplication Performance MonitoringAdministration### Step 1.2: Create APM Domain

  4. Click Create APM Domain

  5. Fill in the details:
    -Name: Production-App-Monitoring (choose a meaningful name)
    -Compartment: Selectyour compartment-Description: "APM for TechHub eCommerce Application"

  6. 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:

  1. APM

Domain OCID

  • Click on yourAPM domain- Copy the OCID(looks like: ocid1.apmdomain.oc1.iad.amaaa...)
  1. 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)
  1. 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!
  1. 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.

  1. Go to:Identity & SecurityIdentityDynamic Groups

  2. Click Create Dynamic Group

  3. Fill in:
    -Name: techhub-compute-dg
    -**Description: "compute instances for TechHub application"

  4. 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

  1. Go to: Identity & SecurityIdentityPolicies

  2. Click Create Policy

  3. Fill in:
    - Name: techhub-compute-policy
    - Description: "Allow compute instances to use OCI services"

  4. 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 just use apm-domains. The manage permission 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

  1. Authentication: Use "authorization": "dataKey <key>" format (NOT "Bearer")

  2. BatchSpanProcessor: Batches spans for better performance

  3. 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)

  1. Correct path: {endpoint}/static/jslib/apmrum.min.js
    3.public key: Use the publicdata key
    (not private!)

  2. 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

  1. Open your application in a browser

  2. Open DevTools (F12)

  3. Go toConsoletab

  4. 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:

  1. Go to:OCI ConsoleObservability & ManagementApplication Performance Monitoring

  2. Select yourAPM domain:Production-App-Monitoring

  3. Click:Trace Explorerin the left menu

  4. Set time range:Last 2 Hours

  5. Remove all filters (or filter by your service name)

  6. 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:

  1. Same location:Trace Explorer

  2. Add filter:Trace Type = Browser

  3. Time range:Last 1 Hour

  4. 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:ApplicationsServers→ Select your service- Shows: Request rates, response times, error rates, throughputDatabase Dashboard:
- Go to:
ApplicationsDatabases- 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

**:

  1. Wrong filename: Using oracleapmapm.js instead of apmrum.min.js
    ```html

```

  1. 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

**:

  1. RUM SDK didn't load (check Network tab for 404)

  2. JavaScript error preventing initialization

  3. 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 ConsoleCost 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

  1. Create custom dashboards: Visualize your KPIs

  2. Synthetic monitoring: Add uptime checks

  3. Distributed tracing: If you have microservices, trace across services

  4. 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!📊🚀