Debugging & Monitoring

This section should help you understand how to debug and monitor your Serve application.

Ray Dashboard

A high-level way to monitor your Ray Serve application is via the Ray Dashboard. See the Ray Dashboard documentation for a detailed overview, including instructions on how to view the dashboard.

Below is an example of what the Ray Dashboard might look like for a Serve deployment:

Here you can see the Serve controller actor, an HTTP proxy actor, and all of the replicas for each Serve deployment. To learn about the function of the controller and proxy actors, see the Serve Architecture page. In this example pictured above, we have a single-node cluster with a deployment named Counter with num_replicas=2.



For an overview of logging in Ray, see Ray Logging.

Ray Serve uses Python’s standard logging facility with the "ray.serve" named logger. By default, logs are emitted from actors both to stderr and on disk on each node at /tmp/ray/session_latest/logs/serve/. This includes both system-level logs from the Serve controller and HTTP proxy as well as access logs and custom user logs produced from within deployment replicas.

In development, logs are streamed to the driver Ray program (the program that calls .deploy() or, or the serve run CLI command) that deployed the deployments, so it’s most convenient to keep the driver running for debugging. For example, let’s run a basic Serve application and view the logs that are emitted. You can run this in an interactive shell like IPython to follow along.

First we call serve.start():

from ray import serve


This produces a few INFO-level log messages about startup from the Serve controller.

2022-04-02 09:10:49,906 INFO -- View the Ray dashboard at
(ServeController pid=67312) INFO 2022-04-02 09:10:51,386 controller 67312 - Using RayInternalKVStore for controller checkpoint and recovery.
(ServeController pid=67312) INFO 2022-04-02 09:10:51,492 controller 67312 - Starting HTTP proxy with name 'SERVE_CONTROLLER_ACTOR:xlehoa:SERVE_PROXY_ACTOR-node:' on node 'node:' listening on ''

Next, let’s create a simple deployment that logs a custom log message when it’s queried:

import logging

logger = logging.getLogger("ray.serve")

class SayHello:
    def __call__(self, *args):"Hello world!")
        return "hi"


Running this code block, we first get some log messages from the controller saying that a new replica of the deployment is being created:

(ServeController pid=67312) INFO 2022-04-02 09:16:13,323 controller 67312 - Adding 1 replicas to deployment 'SayHello'.

Then when we query the deployment, we get both a default access log as well as our custom "Hello world!" message. Note that these log lines are tagged with the deployment name followed by a unique identifier for the specific replica. These can be parsed by a logging stack such as ELK or Loki to enable searching logs by deployment and replica.

handle = SayHello.get_handle()
(SayHello pid=67352) INFO 2022-04-02 09:20:08,975 SayHello SayHello#LBINMh <ipython-input-4-1e8854e5c9ba>:8 - Hello world!
(SayHello pid=67352) INFO 2022-04-02 09:20:08,975 SayHello SayHello#LBINMh - HANDLE __call__ OK 0.3ms

Querying the deployment over HTTP produces a similar access log message from the HTTP proxy:

curl -X GET http://localhost:8000/
(HTTPProxyActor pid=67315) INFO 2022-04-02 09:20:08,976 http_proxy - GET / 200 2.6ms
(SayHello pid=67352) INFO 2022-04-02 09:20:08,975 SayHello SayHello#LBINMh <ipython-input-4-1e8854e5c9ba>:8 - Hello world!
(SayHello pid=67352) INFO 2022-04-02 09:20:08,975 SayHello SayHello#LBINMh - HANDLE __call__ OK 0.3ms

You can also be able to view all of these log messages in the files in /tmp/ray/session_latest/logs/serve/.

To silence the replica-level logs or otherwise configure logging, configure the "ray.serve" logger from inside the deployment constructor:

import logging

logger = logging.getLogger("ray.serve")

class Silenced:
    def __init__(self):

This will prevent the replica INFO-level logs from being written to STDOUT or to files on disk. You can also use your own custom logger, in which case you’ll need to configure the behavior to write to STDOUT/STDERR, files on disk, or both.

Tutorial: Ray Serve with Loki

Here is a quick walkthrough of how to explore and filter your logs using Loki. Setup and configuration is very easy on Kubernetes, but in this tutorial we’ll just set things up manually.

First, install Loki and Promtail using the instructions on It will be convenient to save the Loki and Promtail executables in the same directory, and to navigate to this directory in your terminal before beginning this walkthrough.

Now let’s get our logs into Loki using Promtail.

Save the following file as promtail-local-config.yaml:

  http_listen_port: 9080
  grpc_listen_port: 0

  filename: /tmp/positions.yaml

  - url: http://localhost:3100/loki/api/v1/push

- job_name: ray
  - labels:
    job: ray
    __path__: /tmp/ray/session_latest/logs/serve/*.*

The relevant part for Ray is the static_configs field, where we have indicated the location of our log files with __path__. The expression *.* will match all files, but not directories, which cause an error with Promtail.

We will run Loki locally. Grab the default config file for Loki with the following command in your terminal:


Now start Loki:

./loki-darwin-amd64 -config.file=loki-local-config.yaml

Here you may need to replace ./loki-darwin-amd64 with the path to your Loki executable file, which may have a different name depending on your operating system.

Start Promtail and pass in the path to the config file we saved earlier:

./promtail-darwin-amd64 -config.file=promtail-local-config.yaml

As above, you may need to replace ./promtail-darwin-amd64 with the appropriate filename and path.

Now we are ready to start our Ray Serve deployment. Start a long-running Ray cluster and Ray Serve instance in your terminal:

ray start --head
serve start

Now run the following Python script to deploy a basic Serve deployment with a Serve deployment logger:

import logging
import ray
from ray import serve
import requests


logger = logging.getLogger("ray.serve")

class Counter:
    def __init__(self):
        self.count = 0

    def __call__(self, request):
        self.count += 1"count: {self.count}")
        return {"count": self.count}


for i in range(10):

Now install and run Grafana and navigate to http://localhost:3000, where you can log in with the default username “admin” and default password “admin”. On the welcome page, click “Add your first data source” and click “Loki” to add Loki as a data source.

Now click “Explore” in the left-side panel. You are ready to run some queries!

To filter all these Ray logs for the ones relevant to our deployment, use the following LogQL query:

{job="ray"} |= "Counter"

You should see something similar to the following:


Ray Serve exposes important system metrics like the number of successful and errored requests through the Ray metrics monitoring infrastructure. By default, the metrics are exposed in Prometheus format on each node.

The following metrics are exposed by Ray Serve:




The number of queries that have been processed in this replica.


The number of exceptions that have occurred in the deployment.


The number of times this replica has been restarted due to failure.


The latency for queries in the replica’s queue waiting to be processed.


The latency for queries to be processed.


The current number of queries queued in the deployment replicas.


The current number of queries being processed.


The number of HTTP requests processed.


The number of non-200 HTTP responses.


The number of requests processed by the router.


The number of requests processed by this ServeHandle.


The number of queries for this deployment waiting to be assigned to a replica.


The number of non-200 HTTP responses returned by each deployment.

To see this in action, run ray start --head --metrics-export-port=8080 in your terminal, and then run the following script:

import ray
from ray import serve

import time


def f():


handle = f.get_handle()
while True:

In your web browser, navigate to localhost:8080. In the output there, you can search for serve_ to locate the metrics above. The metrics are updated once every ten seconds, and you will need to refresh the page to see the new values.

For example, after running the script for some time and refreshing localhost:8080 you might see something that looks like:

ray_serve_deployment_processing_latency_ms_count{...,deployment="f",...} 99.0
ray_serve_deployment_processing_latency_ms_sum{...,deployment="f",...} 99279.30498123169

which indicates that the average processing latency is just over one second, as expected.

You can even define a custom metric to use in your deployment, and tag it with the current deployment or replica. Here’s an example:

class MyDeployment:
    def __init__(self):
        self.my_counter = metrics.Counter(
            description=("The number of excellent reqs to this deployment."),
        self.my_counter.set_default_tags({"deployment": serve.get_current_deployment()})

    def call(self, excellent=False):
        if excellent:

See the Ray Metrics documentation for more details, including instructions for scraping these metrics using Prometheus.