- Write a RESTful API with Ballerina
- Write a gRPC service with Ballerina
- Write a GraphQL API with Ballerina
- Work with data using queries in Ballerina
- Build a data service in Ballerina
- Build a Change Data Capture (CDC) service in Ballerina
- Work with Large Language Models (LLMs) using natural expressions
- Deploy Ballerina on Kubernetes
- Manage data persistence with bal persist
- Create your first connector with Ballerina
- Write a workflow with Ballerina
- Write a workflow with a human task
- Handle errors and replay failed activities in workflows
Observability is a measure of how well the internal states of a system can be inferred from the knowledge of its external outputs.
It consists of the three major pillars below.
- Metrics - numeric values that are collected and aggregated over a period of time.
- Tracing - the activities that occur when a request/transaction occurs in the system from the point of entry to exit.
- Logging - text records of activities that occurred with relevant information along with the timestamp.
Observability tools and platforms enable developers to monitor, troubleshoot, and optimize their applications effectively.
Provide observability in Ballerina
Ballerina services and any client connectors are observable by default. HTTP/HTTPS and SQL client connectors use semantic tags to make tracing and metrics monitoring more informative.
This guide demonstrates how to enable Ballerina observability to observe Ballerina services using different tools and platforms. We will use the example given below.
Example: Observe a Ballerina service
Create a new Ballerina package using the bal new command.
$ bal new observability_demo Created new package 'observability_demo' at observability_demo.
Replace the contents of the main.bal file, which is located inside the newly created observability_demo directory, with the following code:
import ballerina/http; import ballerina/log; type Product record {| int id; string name; float price; |}; type OrderRequest record {| int productId; int quantity; |}; type Order record {| int orderId; int productId; int quantity; float totalPrice; |}; // Sample data map<Product> products = { "1": {id: 1, name: "Laptop", price: 1200.00}, "2": {id: 2, name: "Smartphone", price: 800.00}, "3": {id: 3, name: "Headphones", price: 150.00} }; map<Order> orders = {}; int orderCount = 0; @display { label: "Shopping Service" } service /shop on new http:Listener(8090) { // List available products. resource function get products() returns Product[] { log:printInfo("Fetching product list"); return products.toArray(); } // Add a new product. resource function post product(Product product) returns http:Created|http:Conflict|error? { log:printInfo("Adding a new product"); if products.hasKey(product.id.toString()) { log:printError("Product already exists with product ID", id =product.id); http:Conflict errorResponse = { body: string `Product already exists with product ID: ${product.id}` }; return errorResponse; } products[product.id.toString()] = product; log:printInfo("Product added successfully.", product = product); http:Created response = { body: string `Product added successfully with product ID: ${product.id}` }; return response; } // Place a new order. resource function post 'order(OrderRequest orderRequest) returns http:Accepted|http:NotFound|error? { log:printInfo("Received order request"); if !products.hasKey(orderRequest.productId.toString()) { log:printError("Product not found with product ID", id = orderRequest.productId); http:NotFound errorResponse = { body: string `Product not found with product ID: ${orderRequest.productId.toString()}` }; return errorResponse; } Product product = products.get(orderRequest.productId.toString()); Order newOrder = {orderId: orderCount, productId: orderRequest.productId, quantity: orderRequest.quantity, totalPrice: product.price * orderRequest.quantity}; orders[orderCount.toString()] = newOrder; orderCount += 1; log:printInfo("Order placed successfully.", 'order = newOrder); http:Accepted response = { body: newOrder }; return response; } // Get order details by ID. resource function get 'order/[int orderId]() returns http:Ok|http:NotFound|error? { log:printInfo("Fetching order details"); if !orders.hasKey(orderId.toString()) { log:printError("Order not found with order ID", id = orderId); http:NotFound errorResponse = { body: string `Order not found with order ID: ${orderId}` }; return errorResponse; } Order 'order = orders.get(orderId.toString()); log:printInfo("Order details fetched successfully", 'order = 'order); http:Ok response = { body: 'order }; return response; } }
Enable observability for a Ballerina project
Observability can be enabled in a Ballerina project by adding the following section to the Ballerina.toml file.
[build-options] observabilityIncluded=true
Note: The above configuration is included by default in the
Ballerina.tomlfile generated when initiating a new package using thebal newcommand.
Alternatively, we can pass the --observability-included flag with the bal run command to start a Ballerina program with observability enabled.
Setting up Ballerina runtime configurations
To enable observability (both metrics and tracing) in the Ballerina runtime, use the following configurations:
[ballerina.observe] enabled = true provider = <PROVIDER>
Metrics and tracing can be enabled separately as well by using the following configurations. Add additional configurations specific to the tool or platform you are using.
[ballerina.observe] metricsEnabled=true metricsReporter=<METRICS_REPORTER> tracingEnabled=true tracingProvider=<TRACING_PROVIDER>
| Configuration key | Description | Default value | Possible values |
|---|---|---|---|
ballerina.observe.metricsEnabled | Whether metrics monitoring is enabled (true) or disabled (false) | false | true or false |
ballerina.observe.metricsReporter | Reporter name that reports the collected Metrics to the remote metrics server. This is only required to be modified if a custom reporter is implemented and needs to be used. | None | otel, prometheus, newrelic, or if any custom implementation, the name of the reporter. |
ballerina.observe.tracingEnabled | Whether tracing is enabled (true) or disabled (false) | false | true or false |
ballerina.observe.tracingProvider | The tracer name, which implements the tracer interface. | None | otel, jaeger, zipkin, newrelic or the name of the tracer of any custom implementation. |
Common tags in metrics and traces
When observability is enabled, the Ballerina runtime adds a set of common tags (labels) to the metrics and spans it records for service resource functions, service remote functions, client remote calls, client resource calls, workers, and the main function. You can use these tags to filter, group, and correlate telemetry data in your observability tool or platform.
A tag is added only when it applies to the observed function. For example, src.resource.path is added only for resource functions, and error is added only when the function returns an error. Boolean tags are set to true when they apply and are left out otherwise. They are never set to false.
Source tags
These tags describe the function being observed.
| Tag | Description | When it is added | Possible values |
|---|---|---|---|
src.module | The module that contains the observed function, in the <org>/<module>:<version> format. | Always | For example, myorg/observability_demo:0.1.0 |
src.position | The location of the observed function or call in the source code, in the <file-name>:<line>:<column> format. | Always | For example, main.bal:38:5 |
src.function.name | The name of the observed function or the called method. For a client resource call, this is the internal name of the resource method, which combines the accessor and the resource path. | All observations except service resource functions | For example, main, onMessage, get, $get$products |
src.object.name | For a service function, the service name. For a method call on an object (such as a client remote call or a client resource call), the type name of the object, in the <org>/<module>/<type-name> format. | Service functions and method calls on objects | For example, Shopping Service, /shop, ballerina/http/Client |
src.resource.path | The resource path of the observed resource function. A path parameter is shown as ^ and a rest path parameter is shown as ^^. | Service resource functions | For example, /products, /orders/^ |
src.resource.accessor | The accessor of the observed resource function. For HTTP services, this is the HTTP method. | Service resource functions | For example, get, post, put, delete |
src.service.resource | Shows that the observed function is a resource function of a service. | Service resource functions | true |
src.service.remote | Shows that the observed function is a remote function of a service. | Service remote functions | true |
src.client.remote | Shows that the observation is for a remote method call on a client object. | Client remote calls | true |
src.client.resource | Shows that the observation is for a resource method call on a client object (for example, check httpClient->/products). | Client resource calls | true |
src.worker | Shows that the observed function is a worker. | Workers | true |
src.main | Shows that the observed function is the main function of the program. | The main function | true |
Note: The service name is the
labelof the@displayannotation attached to the service (for example,Shopping Servicein the example above). If the annotation is not present, the service base path is used (for example,/shop). If the service has no base path, a generated name in the<org>_<module>_svc_<index>format is used.
Entry point tags
These tags describe the entry point of the execution in which the function was observed. The entry point is the first service resource function, service remote function, or function that started the observation, such as main. Nested observations carry the same entry point tags, so a call made deep in the call stack can be traced back to the entry point that triggered it.
| Tag | Description | When it is added | Possible values |
|---|---|---|---|
entrypoint.function.module | The module of the entry point, in the <org>/<module>:<version> format. | Always | For example, myorg/observability_demo:0.1.0 |
entrypoint.service.name | The name of the service that contains the entry point. | When the entry point is a service resource function or service remote function | For example, Shopping Service, /shop |
entrypoint.function.name | The name of the entry point. For a resource function, this is the resource path. For other functions, this is the function name. | Always | For example, /products, main |
entrypoint.resource.accessor | The accessor of the entry point resource function. | When the entry point is a service resource function | For example, get, post |
Error tag
| Tag | Description | When it is added | Possible values |
|---|---|---|---|
error | Shows that the observed function or call returned an error. | When the observed function or call returns an error | true |
For example, a request to the GET /shop/products resource in the example above is recorded with the following tags:
src.module = myorg/observability_demo:0.1.0 src.position = main.bal:38:5 src.object.name = Shopping Service src.service.resource = true src.resource.accessor = get src.resource.path = /products entrypoint.function.module = myorg/observability_demo:0.1.0 entrypoint.service.name = Shopping Service entrypoint.function.name = /products entrypoint.resource.accessor = get
Note: Connectors such as the HTTP and SQL clients and listeners add their own tags on top of these common tags (for example,
http.method,http.url, andhttp.status_code_group).
Observability tools and platforms supported by Ballerina
This outlines how to enable and configure observability in Ballerina for various tools and platforms. It provides a step-by-step guide for setting up monitoring, tracing, and logging using widely used observability solutions.
Observability tools and platforms help monitor and analyze application performance, identify issues, and ensure reliability. The following are the main observability tools and platforms supported by Ballerina:
-
OpenTelemetry: A vendor-neutral standard for exporting traces and metrics over the OpenTelemetry Protocol (OTLP) to any compatible backend.
-
Prometheus: A monitoring system and time-series database for metrics collection and alerting.
-
Jaeger: A distributed tracing platform for monitoring and debugging microservices.
-
Zipkin: A distributed tracing system to collect and look up trace data.
-
New Relic: A full-stack observability platform for application performance monitoring (APM) and telemetry.
-
Datadog: A cloud-based observability service offering monitoring, metrics, traces, and logging.
-
Elastic Stack: A collection of tools (Elasticsearch, Logstash, Kibana) for centralized logging and analytics.
The following contains a guide to set up and observe Ballerina programs in each of the observability tools or platforms mentioned above.
- Observe Ballerina programs with OpenTelemetry
- Observe Ballerina programs with Prometheus
- Observe Ballerina programs with Jaeger
- Observe Ballerina programs with Zipkin
- Observe Ballerina programs with New Relic
- Observe Ballerina programs with Datadog
- Observe Ballerina programs with Elastic Stack
- Observe Ballerina programs with OpenSearch
- Observe Ballerina programs with Moesif