Heroku: add-ons, logs and monitoring

Paweł Ćwik

We have updated this text for you!
Update date: 31.12.2024
Author of the update: Marcin Majgier

In a previous article, I introduced you to the basics of Heroku and demonstrated how to deploy Spring Boot based application. In the second part I will show you how to add database support and delve into logs and metrics.

Heroku offers an add-on system that allows you to integrate additional services into your application, such as databases, message queues, caching systems, storage, email services, and more. They are provided by third-party vendors, and you can explore a wide range of options in the Heroku Marketplace.

Adding database support for Heroku-based application

PostgreSQL is no longer available for free, but it remains affordable, with the basic plan starting at 0.007$ per hour (approximately 5$ per month). To add database support, you can use the web interface, search for PostgreSQL in Heroku marketplace and click “Install Heroku Postgres”.

Alternatively, we can use CLI and execute the following command:

heroku addons:create heroku-postgresql:essential-0

You will be informed that the installation process started:

Creating heroku-postgresql:essential-0 on ⬢ sheltered-beach-59560... ~$0.007/hour (max $5/month)
Database should be available soon
postgresql-regular-38138 is being created in the background. The app will restart when complete...
Use heroku addons:info postgresql-regular-38138 to check creation progress
Use heroku addons:docs heroku-postgresql to view documentation
MacBook-Pro-majgierm:heroku-demo marcin$ heroku addons:info postgresql-regular-38138
=== postgresql-regular-38138

After this command finishes our database would be created and ready for use. Next we need to configure our application with suitable database URL:

heroku config

Will show us our database URL and it would not be pretty, it can look similar to this presented below:

postgres://qamtrwcghvuzfm:e5cc937842c248126e9dba…@c8m0261h0c7ik.cluster-czrs8kj4isg7.us-east-1.rds.amazonaws.com:5432/d958kfmhjqnnh6

But don’t worry – you won’ teven need to copy and paste it. For now, let’s set this aside and switch to our codebase to implement persistence.

Extend our application

Let start with adding these three dependencies to pom.xml (heroku project created in the previous article):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.4</version>
</dependency>

The spring-boot-starter-data-jpa dependency enables us to use Spring Data repositories and we don’t need to write boilerplate CRUD code. The spring-boot-starter-data-rest exposes autimatically our repositories as REST endpoints. Finally, the last dependency is the JDBC driver for PostgreSQL.

Let’s now create standard Person entity

package com.jlabs.heroku_demo;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;
    private String name;

    private Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

}With the entity in place, we can create spring-data repository:

package com.jlabs.heroku_demo;

import org.springframework.data.repository.CrudRepository;

public interface PeopleRepository extends CrudRepository<Person, Long> {
}

Now extend our controller from the previous article and thymeleaf template to make use of persisted data:

package com.jlabs.heroku_demo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HelloController {

    private final PeopleRepository peopleRepository;

    public HelloController(PeopleRepository peopleRepository) {
        this.peopleRepository = peopleRepository;
    }

    @RequestMapping("hi")
    public String hello(Model model) {
        model.addAttribute("people", peopleRepository.findAll());
        return "hello";
    }
}
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Clockwork Java Hello</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <p th:each="person : ${people}" th:text="${person.name}"/>
    </body>
</html>

Add database settings to the application

And now in application.properties configuration file, add the following:

Spring.datasource.url=${JDBC_DATABASE_URL}
spring.datasource.username=${JDBC_DATABASE_USERNAME}
spring.datasource.password=${JDBC_DATABASE_PASSWORD}
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update

You might notice references to environmental variables, such as  JDBC_DATABASE_URL. What’s the deal with those?

Heroku’s dyno (Unix container where our application runs) provide environment variables containing credentials, addresses, ports and other configuration details for our add-on services. This means we don’t have to hard-code JDBC URL provided by ‘heroku config’, instead we can use provided system variables.

Now deploy the changed application (git commit; heroku login; git push heroku master) and go to https://sheltered-beach-59560-21a465b93ca9.herokuapp.com. 

You should see information about REST endpoints provided by spring data rest:

{
  “_links”: {
    “persons”: {
      “href”: “https://sheltered-beach-59560-21a465b93ca9.herokuapp.com/persons”
    },
    “profile”: {
      “href”: “https://sheltered-beach-59560-21a465b93ca9.herokuapp.com/profile”
    }
  }
}
}

Now use curl, postman or any other tool to call POST requests to https://sheltered-beach-59560-21a465b93ca9.herokuapp.com/persons endpoint and persist some data. In curl it can be like below:

curl -X POST https://sheltered-beach-59560-21a465b93ca9.herokuapp.com/persons \

-H “Content-Type: application/json” \

-d ‘{ “name”: “John” }’

Now when we visit https://sheltered-beach-59560-21a465b93ca9.herokuapp.com/hi page, we have a list of all people we just added.

And that’s it! In just a few simple steps, we’ve added database support to our cloud-based application.

Logs

Heroku provides simple access to timestamp-ed logs via CLI command:
heroku logs

2024-12-30T15:48:38.267743+00:00 heroku[router]: at=info method=HEAD path=”/” host=sheltered-beach-59560-21a465b93ca9.herokuapp.com request_id=0dc97b17-9019-4737-9612-c48578f1b9f2 fwd=”104.47.51.254″ dyno=web.1 connect=0ms service=58ms status=204 bytes=162 protocol=https

2024-12-30T15:50:39.571169+00:00 heroku[router]: at=info method=POST path=”/persons” host=sheltered-beach-59560-21a465b93ca9.herokuapp.com request_id=57fffa5b-e03c-4cf0-a446-f923d8324ac1 fwd=”83.10.27.53″ dyno=web.1 connect=0ms service=148ms status=201 bytes=569 protocol=https

2024-12-30T15:52:25.041531+00:00 app[web.1]: Hibernate: select p1_0.id,p1_0.name from person p1_0

2024-12-30T15:52:25.301385+00:00 heroku[router]: at=info method=GET path=”/hi” host=sheltered-beach-59560-21a465b93ca9.herokuapp.com request_id=2efe269c-7b37-4ef5-9519-026841d48dba fwd=”52.123.138.164″ dyno=web.1 connect=0ms service=343ms status=200 bytes=374 protocol=https

2024-12-30T15:52:26.566648+00:00 app[web.1]: Hibernate: select p1_0.id,p1_0.name from person p1_0

Each entry is formatted in the same way ie: timestamp source[dyno]: message. While timestamp and message are self-explanatory, the source and dyno part are heroku-specific. Source determines if log was from one of our dynos – then it’s named ‘app’ and if from heroku’s system component (like HTTP router or dyno manager) have the source ‘heroku’

Dyno is the name of the dyno component that wrote the log line (web.1) or, in case of heroku system components it’s ‘router’ or ‘manager’

We can apply some filters to logs with a certain source, a certain dyno, or both, you can use the –source (or -s) and –dyno (or -d) arguments.

heroku logs –dyno web.1
heroku logs –source heroku

You can also combine the filtering switches with –tail to get a real-time stream of filtered output.

heroku logs –source app –tail

The logs command retrieves 100 log lines by default. You can specify the number of log lines to retrieve (up to a maximum of 1,500 lines) by using the –num (or -n) option.

heroku logs -n 200

Metrics

Since all Heroku plans are now paid, now we have access to basic metrics directly from Heroku dashboard. These metrics include:

  • Memory usage: how much memory our application is consuming over time
  • Response time: monitor the average response time
  • Request per minute: track the number of incoming requests

In the Eco plan all metrics are available in 24 hours resolution, providing insights into your app’s performance and helping identify potential issues, related to your application. In higher plans (Standard or higher) there are additional options for time resolution as well as possibility to set up threshold alerting, eg. notifications for high response times or memory usage, enabling you to proactively manage our application’s performance.

Summary

In the second article, we explained how to enhance a Heroku-deployed Spring Boot application by integrating the PostgreSQL add-on and extending Heroku demo project with persistence and simple CRUD operations. We also demonstrated the way to access logs and use simplified monitoring and metrics functions.

Meet the geek-tastic people, and allow us to amaze you with what it's like to work with j‑labs!

Contact us