Heroku: add-ons, logs and monitoring
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.
Code of sample application with step-by-step instructions can be found at https://github.com/clockworkjava/herokudemo