Skip to content

Commit 50c49ee

Browse files
weltekialexellis
authored andcommitted
Add extra section to show instumentation of db calls
Signed-off-by: Han Verstraete (OpenFaaS Ltd) <han@openfaas.com>
1 parent 6842162 commit 50c49ee

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed

_posts/2025-05-12-trace-functions-with-opentelemetry.md

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,177 @@ curl -i "http://127.0.0.1:8080/function/order?id=1"
524524
![Screenshot of the Grafana Explorer showing an invocation trace with a custom span](/images/2025-05-opentelemetry/manual-span-trace.png)
525525
> Trace detail showing the function invocation with the custom span. The span has the order id attribute and events that we added to it.
526526

527+
## Capture traces from libraries and frameworks
528+
529+
Traces and auto-instrumentation can become really valuable when you have more complex function workflows where functions are chained together or interact with external systems. These functions most likely use third-party Python libraries to perform actions like query a database, make HTTP requests, talk to a message broker or key value store, etc. Auto-instrumentation allows you to capture traces from many popular libraries without changing your code.
530+
531+
We will expand the order function to simulate this kind of workflow. The order function will look up an order in a PostgreSQL database and invoke a second function, `order-confirmation` when it is done processing the order.
532+
533+
To follow along with this example you can quickly deploy a PostgreSQL database in your cluster using [arkade](https://github.com/alexellis/arkade?tab=readme-ov-file#getting-arkade):
534+
535+
```sh
536+
arkade install postgresql
537+
```
538+
arkade prints out connection instructions and the command to get the database password. Connect to the database and seed it with an orders table and some dummy content.
539+
540+
Create the `orders` table:
541+
542+
```sql
543+
CREATE TABLE orders (
544+
id SERIAL PRIMARY KEY,
545+
customer_name VARCHAR(100) NOT NULL,
546+
order_date DATE NOT NULL DEFAULT CURRENT_DATE,
547+
total_amount NUMERIC(10, 2) NOT NULL
548+
);
549+
```
550+
551+
Insert a sample order:
552+
553+
```sql
554+
INSERT INTO orders (customer_name, order_date, total_amount)
555+
VALUES ('Alice Johnson', '2025-05-12', 149.99);
556+
```
557+
558+
[Psycopg](https://www.psycopg.org/docs/) will be used to query the Postgresql database and the [Requests](https://requests.readthedocs.io/en/latest/) package to make HTTP requests. Both packages need to be appended to the `requirements.txt` file for the order function.
559+
560+
```diff
561+
opentelemetry-distro
562+
opentelemetry-exporter-otlp
563+
+psycopg2-binary
564+
+requests
565+
```
566+
567+
Update the `handler.py` to get orders from the database and invoke the `order-confirmation` function.
568+
569+
```diff
570+
import time
571+
from opentelemetry import trace
572+
+ import psycopg2
573+
+ import requests
574+
575+
# Get a tracer
576+
tracer = trace.get_tracer(__name__)
577+
578+
+ # Database connection settings
579+
+ db_config = {
580+
+ 'dbname': 'postgres',
581+
+ 'user': 'postgres',
582+
+ 'password': '<replace_with_db_password>',
583+
+ 'host': 'postgresql.default.svc.cluster.local',
584+
+ 'port': '5432' # default PostgreSQL port
585+
+ }
586+
587+
+ confirmation_url = 'http://gateway.openfaas.svc.cluster.local:8080/function/order-confirmation'
588+
589+
def handle(event, context):
590+
process_order(event.query['id'])
591+
+ # Send confirmation request
592+
+ requests.post(confirmation_url, json={'order_id': event.query['id']})
593+
594+
return {
595+
"statusCode": 200,
596+
"body": "Order processed"
597+
}
598+
599+
def process_order(order_id):
600+
# Manually create a span
601+
with tracer.start_as_current_span("process_order"):
602+
# you can also add attributes or events
603+
trace.get_current_span().set_attribute("order.id", order_id)
604+
trace.get_current_span().add_event("Order processing started")
605+
606+
+ # Get order from database
607+
+ order = get_order_from_db(order_id)
608+
609+
# simulate some work for the order
610+
time.sleep(0.4)
611+
612+
result = "done"
613+
trace.get_current_span().add_event("Order processing finished")
614+
return result
615+
616+
+ def get_order_from_db(order_id):
617+
+ try:
618+
+ # Connect to the database
619+
+ conn = psycopg2.connect(**db_config)
620+
+ cursor = conn.cursor()
621+
+
622+
+ # Execute a query to get the order
623+
+ cursor.execute("SELECT * FROM orders WHERE id = %s", (order_id,))
624+
+
625+
+
626+
+ order = cursor.fetchone()
627+
+ return order
628+
+
629+
+ except psycopg2.Error as e:
630+
+ print("Database error:", e)
631+
+ return None
632+
+
633+
+ finally:
634+
+ if 'cursor' in locals():
635+
+ cursor.close()
636+
+ if 'conn' in locals():
637+
+ conn.close()
638+
```
639+
640+
Add a new function `order-confirmation` and update the `stack.yaml` to configure the OpenTelemetry agent for the function.
641+
642+
```sh
643+
faas-cli new order-confirmation --lang python3-http --append stack.yaml
644+
```
645+
646+
```yaml
647+
functions:
648+
order:
649+
lang: python3-http
650+
handler: ./order-confirmation
651+
image: ${REPO:-ttl.sh}/${OWNER:-openfaas}/order-confirmation:0.0.1
652+
environment:
653+
OTEL_SERVICE_NAME: order-confirmation.${NAMESPACE:-openfaas-fn}
654+
OTEL_TRACES_EXPORTER: ${EXPORTER:-otlp}
655+
OTEL_METRICS_EXPORTER: none
656+
OTEL_LOGS_EXPORTER: none
657+
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-alloy.monitoring.svc.cluster.local:4317}
658+
OTEL_EXPORTER_OTLP_TRACES_INSECURE: true
659+
```
660+
661+
In a real application this function would perform jobs like sending out the order confirmation via email. To keep things simple we are just going to sleep a couple of milliseconds to simulate some work.
662+
663+
```python
664+
import time
665+
666+
def handle(event, context):
667+
668+
# simulate sending order confirmation email
669+
time.sleep(0.2)
670+
671+
return {
672+
"statusCode": 200,
673+
"body": "Confirmation order"
674+
}
675+
```
676+
677+
Deploy both functions:
678+
679+
```sh
680+
faas-cli up
681+
```
682+
683+
Invoke the order function and view the trace in Grafana.
684+
685+
```sh
686+
curl -i "http://127.0.0.1:8080/function/order?id=1"
687+
```
688+
689+
![Screenshot of a trace visualization for the order function in Grafana](/images/2025-05-opentelemetry/db-access-trace-detail.png)
690+
> Screenshot of a trace visualization for the order function in Grafana
691+
692+
The trace includes 5 spans. We can clearly see the different operations the function performed and how much time they took. The top level span for the GET request to `orders.openfaas-fn` shows the complete function invocation took 616.73ms. As children of that span we see our custom `process_order` span and the `POST` request made to the `order-confirmation` function. We also get a span with the details of the database query.
693+
694+
Note that we did not have to make any changes to get traces from the `psycopg2` and `requests` package. The auto-instrumentation took care of that.
695+
696+
The Python agent by default will detect a Python program’s packages and instrument any packages it can. This makes instrumentation easy, but can also result in too much or unwanted data. You can omit specific packages from instrumentation by using the `OTEL_PYTHON_DISABLED_INSTRUMENTATIONS` environment variable. See: [Disabling Specific Instrumentations](https://opentelemetry.io/docs/zero-code/python/configuration/) for more details.
697+
527698
## OpenTelemetry on OpenFaaS Edge
528699

529700
Collecting telemetry is also supported on [OpenFaaS Edge](https://docs.openfaas.com/edge/overview/). OpenFaaS Edge is a lightweight option for running OpenFaaS functions that does not use Kubernetes, ideal for edge computing environments.

0 commit comments

Comments
 (0)