Heroku: add-ons, logs and monitoring

Paweł Ćwik

In a previous article I’ve introduced you to Heroku basics and deployed spring-boot-based application there. In the second part I will show you how to add database support and get a little bit into logs and metrics.

Heroku provides add-on system to provide additional services for our application such as databases, messaging queues, caching systems, storage, email services and so on. They are provided (usually) by third parties – there is even marketplace for them (https://elements.heroku.com/addons).

Adding database support for Heroku-based application

If we want to add database support (PostgreSQL is available for free) we simply type in the command in Heroku CLI:

heroku addons:create heroku-PostgreSQL

After this command finishes our database would be created and ready for use. Command

heroku config
Will show us our database URL and it would not be pretty

postgres://qamtrwcghvuzfm:e5cc937842c248126e9dba981e55159d4233f0a690c8abaa5f76ad7984ba6

But fear not, you don’t even have to copy-paste it. Let’s leave this for now and switch to our code base to make use of persistence.

First of all let’s add three dependencies to pom.xml file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>1.5.1.RELEASE</version>
</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.1.4</version>
</dependency>

Spring-boot-starter-data-jpa will enable us to use spring data repositories to relief us of the need to write any boilerplate CRUD code. Spring-boot-starter-data-rest will automagically expose our repositories as REST endpoints. The last one is the JDBC driver for PostgreSQL.

And now create standard Person entity

@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;
    }
}

Do not forget empty constructor. With our entity in place we can create spring-data repository

public interface PeopleRepository extends CrudRepository<Person, Long> {

}

Now let’s extend our controller and thymeleaf template a little bit to make use of persisted data:

@Controller
public class HelloController {

    PeopleRepository peopleRepository;

    @Autowired
    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>

And now to application.properties configuration file

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 calls to environmental variables – JDBC_DATABASE_URL and so on. What is the deal with those?

Well – heroku’s dyno (Unix container on which our app is running) provides environmental variables with credentials, addresses, ports and so on to our add-on services. So in this case we don’t have to hard-code ugly JDBC URL provided by ‘heroku config’ but instead we use provided system variables.

Now deploy the changed application (git commit;heroku login; git push heroku master) and go to https://radiant-island-50498.herokuapp.com/ and you should see information about REST endpoints provided by spring data rest (and please don’t mind ‘persons’ – it is just a demo, right?)

{
  "_links" : {
    "persons" : {
      "href" : "https://radiant-island-50498.herokuapp.com/persons"
    },
    "profile" : {
      "href" : "https://radiant-island-50498.herokuapp.com/profile"
    }
  }
}

Now use curl, postman or any other tool of your liking to call some POST requests to https://radiant-island-50498.herokuapp.com/persons endpoint to persist some data. Body should like

{ “name”: “John” }

And do not forget to set proper content-type header.

Now when we visit https://radiant-island-50498.herokuapp.com/hi page we will have a list of all people we just added.

And that’s it. In few simple steps we added database support for our cloud-based application.

Logs

Heroku provides access to timestamped logs via heroku logs command

2017-12-20T12:14:13.241355+00:00 app[web.1]: 2017-12-24 12:14:13.241 INFO 4 --- [io-57474-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-12-24T12:14:13.241454+00:00 app[web.1]: 2017-12-24 12:14:13.241 INFO 4 --- [io-57474-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-12-20T12:14:13.364319+00:00 app[web.1]: 2017-12-24 12:14:13.363 INFO 4 --- [io-57474-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 122 ms
2017-12-20T12:14:14.266844+00:00 heroku[router]: at=info method=GET path="/" host=radiant-island-50498.herokuapp.com request_id=55f80a5c-fe8d-4d6a-bed0-3b19e0f94a06 fwd="185.125.

Format for each entry – 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’

Of course 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

Sadly for free accounts Heroku does not provide metrics gathering – at least cheapest paid plan is needed. If we upgrade the account level we will have access to some basic metrics from customer dashboard available at heroku’s homepage – memory usage, response time and requests per minute. Metrics from last 24 hours are available. Additionally we can set up basic alerting.

Heroku metrics

Code of sample application with step-by-step instructions can be found at https://github.com/clockworkjava/herokudemo

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami