Understanding output of free in Ubuntu 16.04

Understanding output of free in Ubuntu 16.04

This post explains output of free command in Ubuntu 16.04 paying special attention to information missing in documentation.

Understanding output

Let’s look at free output in humanreadable, wide mode:

free -hw

        total   used   free   shared   buffers   cache   available
Mem:     2.0G   566M   860M      14M       41M    531M        1.2G
Swap:    4.0G     0B   4.0G

Note that output of free command in Ubuntu 16.04 differs from previous versions.

There are two lines in the output. Mem row shows phisical memory stats. Swap line displays swap stats.

Phisical memory

total: Total installed memory.

used: Cacluclated as total - free - buffers - cache.

free: Unused memory.

shared: Mostly memory occupied by tmpfs, but also includes memory shared among several processeses via IPC.

Linux tmpfs is a temporary file system stored in memory instead of a persistent storage. Mounting directory as tmpfs can be an effective way of speeding up file access. Also it ensures that directory contents is automatically cleared upon reboot.

buffers: In-memory block I/O buffers, not included in cache. They are relatively short-lived, small, and can be neglected. These buffers store non-file data blocks and are not represented in the Page Cache.

cache: Memory used by the Page Cache and slabs. Page Cache is the best candidate to shrink when system needs more memory.

Page Cache

Prior to Linux kernel version 2.4, Linux had two separate caches: page cache and buffer cache. Page cache stored file data. Buffer cache kept disk blocks. In practice, most files live in disk filesystem, so the same data was cached twice, once in each cache.

To avoid duplication, Linux kernel version 2.4 merged page cache with part of buffer cache keeping page cache data blocks and called it Page Cache. From that version, buffer cache significantly shrank and only keeps non-file data blocks.

Slabs

Linux kernel allocates and deallocates lots of data objects during operation. Kernel objects often require memory chunks of the same size. To eliminate fragmentation and significantly improve performance, kernel preallocates and caches these objects. A slab is the amount by which such cache can grow or shrink.

available: Estimation of memory available for starting new applications without swapping. It takes into account Page Cache and that not all reclaimable memory slabs will be reclaimed, because they are being in use.

Swap

total: Total swap in system.

buffers: Same as memory, but in swap.

cache: Same as memory, but in swap.

Demystifying ifconfig and network interfaces in Linux

Demystifying ifconfig and network interfaces in Linux

This post explains ifconfig output of common developer’s box, paying special attention to parameters poorly explained in official documentation. It also slightly touches Linux network interfaces.

ifconfig

ifconfig is a command line tool for UNIX-like systems that allows for diagnosing and configuring network interfaces. At boot time, it sets up network interfaces such as Loopback and Ethernet. Most of the time, however, ifconfig is used for network diagnostics.

Before diving into details of its output, let’s first make clear what is an interface.

network interface

A network interface is a software interface to networking hardware. Linux kernel distinguishes between two types of network interfaces: physical and virtual.

Physical network interface represents an actual network hardware device such as network interface controller (NIC). In practice, you’ll often find eth0 interface, which represents Ethernet network card.

Virtual network interface doesn’t represent any hardware device and is usually linked to one. There are different kinds of virtual interfaces: Loopback, bridges, VLANs, tunnel interfaces and so on. With proliferation of software defined networks, virtual interfaces become wildly used.

Demystifying ifconfig output

Let’s have a look at ifconfig output for a developer’s box with installed Ubuntu and Docker.

$ ifconfig

docker0   Link encap:Ethernet  HWaddr 02:42:2d:66:fc:f1  
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:2dff:fe66:fcf1/64 Scope:Link
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:2 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:152 (152.0 B)  TX bytes:258 (258.0 B)

eth0      Link encap:Ethernet  HWaddr 08:00:27:31:65:b5  
          inet addr:10.0.2.15  Bcast:10.0.2.255  Mask:255.255.255.0
          inet6 addr: fe80::3db9:eaaa:e0ae:6e09/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:1089467 errors:0 dropped:0 overruns:0 frame:0
          TX packets:508121 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:903808796 (903.8 MB)  TX bytes:31099448 (31.0 MB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:9643 errors:0 dropped:0 overruns:0 frame:0
          TX packets:9643 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:719527 (719.5 KB)  TX bytes:719527 (719.5 KB)

interfaces

There are 3 network interfaces on the box:

eth0 is a physical interface representing Ethernet network card. It’s used for communication with other computers on the network and on the Internet.

lo is a special virtual network interface called loopback device. Loopback is used mainly for diagnostics and troubleshooting, and to connect to services running on local host.

docker0 is a virtual bridge interface created by Docker. This bridge creates a separate network for docker containers and allows them to communicate with each other.

interface details

Let’s look closely at details of ifconfig output:

Link encap shows how packets are encapsulated for transmission. Most interfaces wrap packets in Ethernet frames.

HWaddr is hardware address of the ethernet interface (also known as MAC address).

inet addr is IPv4 address assigned to the interface.

Bcast is broadcast address for the interface.

Mask is network mask for the interface.

inet6 addr is IPv6 address assigned to the interface.

Scope is scope of IPv6 address. It can be link-local or global. Link-local address is used in local area network and is not routable. Global address is routable.

UP indicates that kernel modules related to the interface have been loaded and interface is activated.

BROADCAST indicates that interface is configured to handle broadcast packets, which is required for obtaining IP address via DHCP.

RUNNING indicates that interface is ready to accept data.

MULTICAST indicates that interface supports multicasting.

MTU is maximum transmission unit. IP datagrams larger than MTU bytes will be fragmented into multiple Ethernet frames.

Metric determines the cost of using the interface. Interfaces with lower cost have higher priority.

interface stats

RX packets is a total number of packets received.

RX errors shows a total number of packets received with error. This includes too-long-frames errors, ring-buffer overflow errors, CRC errors, frame alignment errors, fifo overruns, and missed packets.

RX dropped is a number of dropped packets due to unintended VLAN tags or receiving IPv6 frames when interface is not configured for IPv6.

RX overruns is a number of received packets that experienced fifo overruns, caused by rate at which a buffer gets full and kernel isn’t able to empty it.

RX frame is a number of misaligned frames, i.e. frames with length not divisible by 8.

TX packets is total number of packets transmitted.

TX errors, TX dropped and TX overruns are similar to RX equivalents.

TX carriers is a number of packets that experienced loss of carriers. This usually happens when link is flapping.

TX collisions is a number of transmitted packets that experienced Ethernet collisions.

TX txqueuelen is length of transmission queue.

RX bytes is a total number of bytes received over interface.

TX bytes is a total number of bytes transmitted over interface.

Summary

Despite being superseded by ip command, ifconfig is still commonly used and provides lots of useful details about network interfaces, both physical and virtual.

Polishing Your Curl Expertise

Polishing Your Curl Expertise

Previous post covers bare minimum of curl you need to know for testing RESTful microservices. Read it first if you need basics. This writing focuses on corner cases and advanced options, making curl experience more enjoyable.

Microservice for experiments

For demonstrations, I’ve created a simple RESTful microservice in Golang. Use it if you have nothing to experiment with.

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
)

type RequestSummary struct {
	URL     string
	Method  string
	Headers http.Header
	Params  url.Values
	Auth    string
	Body    string
}

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		bytes, err := ioutil.ReadAll(r.Body)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		auth := ""
		if user, pass, ok := r.BasicAuth(); ok {
			auth = user + ":" + pass
		}

		rs := RequestSummary{
			URL:     r.URL.RequestURI(),
			Method:  r.Method,
			Headers: r.Header,
			Params:  r.URL.Query(),
			Auth:    auth,
			Body:    string(bytes),
		}

		resp, err := json.MarshalIndent(&rs, "", "\t")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Write(resp)
		w.Write([]byte("\n"))
	})

	http.ListenAndServe(":8080", nil)
	fmt.Println("Exiting...")
}

Suppressing progress meter

curl displays progress meter while fetching data and hides it when data transfer is over. Seeing progress is nice, but it causes an issue when you pipe curl with less for reading a long response. When you do it, progress meter mixes with response, making output clumsy:

$ curl -X GET http://localhost:8080/something | less

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   171  100   171    0     0   4228      0 --:--:-- --:--:-- --:--:--  4275
{
        "URL": "/something",
        "Method": "GET",
        "Headers": {
                "Accept": [
                        "*/*"
                ],
                "User-Agent": [
                        "curl/7.43.0"
                ]
        },
        "Params": {},
        "Auth": null,
        "Body": ""
}

To suppress progress meter, curl can be switched to silent mode with -s (or --silent) option:

$ curl -X GET -s http://localhost:8080/something | less

{
        "URL": "/something",
        "Method": "GET",
        "Headers": {
                "Accept": [
                        "*/*"
                ],
                "User-Agent": [
                        "curl/7.43.0"
                ]
        },
        "Params": {},
        "Auth": null,
        "Body": ""
}

This solves issue with progress meter, but introduce another one - silent mode also suppresses error output:

$ curl -X GET -s http://unknownhost:8080/something | less

(There should have been an error that host cannot be resolved)

A full solution is to use -s together with -S (or --show-error), which displays errors in silent mode:

$ curl -X GET -s -S http://unknownhost:8080/something | less

curl: (6) Could not resolve host: unknownhost

Authentication

To authenticate a user with a password, use -u <user>:<password> (or --user) user:password option.

Don’t pass password in command line though, unless you really know what you do. This is insecure, because executed command line (including your password) will be stored in history and log.

The right way is to pass user only. When password is not provided, curl will ask for it and let you type it securely:

$ curl -X GET -u login http://localhost:8080/something
Enter host password for user 'login':
{
    "URL": "/something",
    "Method": "GET",
    "Headers": {
        "Accept": [
            "*/*"
        ],
        "Authorization": [
            "Basic bG9naW46cGFzc3dvcmQ="
        ],
        "User-Agent": [
            "curl/7.43.0"
        ]
    },
    "Params": {},
    "Auth": "login:password",
    "Body": ""
}

Outputting metadata

Sometimes you need to output information about response, instead of response itself. Option -w <format> (or --write-out <format>) lets you fully control the output and is often used to display response metadata, e.g. HTTP status code:

$ curl -X GET -w "HTTP code is %{http_code}\n" -o /dev/null -Ss http://localhost:8080/something
HTTP code is 200

Note variable http_code is specified as %{http_code}. See curl documentation for list of available variables.

When you control output with -w option, response is often not needed and suppressed, e.g. with -o /dev/null.

Debugging

When something goes wrong, the first step is usually to look at what is sent to endpoint and what returns back. You can do it with -v (or --verbose) option:

curl -X GET -v http://localhost:8080/something
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /something HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 23 Aug 2016 22:49:21 GMT
< Content-Length: 169
< Content-Type: text/plain; charset=utf-8
<
{
    "URL": "/something",
    "Method": "GET",
    "Headers": {
        "Accept": [
            "*/*"
        ],
        "User-Agent": [
            "curl/7.43.0"
        ]
    },
    "Params": {},
    "Auth": "",
    "Body": ""
}
* Connection #0 to host localhost left intact

Tracing

When debugging doesn’t help and you need to dig deeper, try --trace <file> instead:

$ curl -X GET --trace t.txt http://localhost:8080/something
{
    "URL": "/something",
    "Method": "GET",
    "Headers": {
        "Accept": [
            "*/*"
        ],
        "User-Agent": [
            "curl/7.43.0"
        ]
    },
    "Params": {},
    "Auth": "",
    "Body": ""
}

$ cat t.txt
== Info:   Trying ::1...
== Info: Connected to localhost (::1) port 8080 (#0)
=> Send header, 87 bytes (0x57)
0000: 47 45 54 20 2f 73 6f 6d 65 74 68 69 6e 67 20 48 GET /something H
0010: 54 54 50 2f 31 2e 31 0d 0a 48 6f 73 74 3a 20 6c TTP/1.1..Host: l
0020: 6f 63 61 6c 68 6f 73 74 3a 38 30 38 30 0d 0a 55 ocalhost:8080..U
0030: 73 65 72 2d 41 67 65 6e 74 3a 20 63 75 72 6c 2f ser-Agent: curl/
0040: 37 2e 34 33 2e 30 0d 0a 41 63 63 65 70 74 3a 20 7.43.0..Accept:
0050: 2a 2f 2a 0d 0a 0d 0a                            */*....
<= Recv header, 17 bytes (0x11)
0000: 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d HTTP/1.1 200 OK.
0010: 0a                                              .
<= Recv header, 37 bytes (0x25)
0000: 44 61 74 65 3a 20 54 75 65 2c 20 32 33 20 41 75 Date: Tue, 23 Au
0010: 67 20 32 30 31 36 20 32 32 3a 34 33 3a 30 35 20 g 2016 22:43:05
0020: 47 4d 54 0d 0a                                  GMT..
<= Recv header, 21 bytes (0x15)
0000: 43 6f 6e 74 65 6e 74 2d 4c 65 6e 67 74 68 3a 20 Content-Length:
0010: 31 36 39 0d 0a                                  169..
<= Recv header, 41 bytes (0x29)
0000: 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 Content-Type: te
0010: 78 74 2f 70 6c 61 69 6e 3b 20 63 68 61 72 73 65 xt/plain; charse
0020: 74 3d 75 74 66 2d 38 0d 0a                      t=utf-8..
<= Recv header, 2 bytes (0x2)
0000: 0d 0a                                           ..
<= Recv data, 169 bytes (0xa9)
0000: 7b 0a 09 22 55 52 4c 22 3a 20 22 2f 73 6f 6d 65 {.."URL": "/some
0010: 74 68 69 6e 67 22 2c 0a 09 22 4d 65 74 68 6f 64 thing",.."Method
0020: 22 3a 20 22 47 45 54 22 2c 0a 09 22 48 65 61 64 ": "GET",.."Head
0030: 65 72 73 22 3a 20 7b 0a 09 09 22 41 63 63 65 70 ers": {..."Accep
0040: 74 22 3a 20 5b 0a 09 09 09 22 2a 2f 2a 22 0a 09 t": [...."*/*"..
0050: 09 5d 2c 0a 09 09 22 55 73 65 72 2d 41 67 65 6e .],..."User-Agen
0060: 74 22 3a 20 5b 0a 09 09 09 22 63 75 72 6c 2f 37 t": [...."curl/7
0070: 2e 34 33 2e 30 22 0a 09 09 5d 0a 09 7d 2c 0a 09 .43.0"...]..},..
0080: 22 50 61 72 61 6d 73 22 3a 20 7b 7d 2c 0a 09 22 "Params": {},.."
0090: 41 75 74 68 22 3a 20 22 22 2c 0a 09 22 42 6f 64 Auth": "",.."Bod
00a0: 79 22 3a 20 22 22 0a 7d 0a                      y": "".}.
== Info: Connection #0 to host localhost left intact
Using Curl For Ad Hoc Testing Of RESTful Microservices

Using Curl For Ad Hoc Testing Of RESTful Microservices

There are plenty of tools available for testing RESTful microservices today. Most of them, e.g. SoapUI, are comprehensive solutions and best fit for creating testing suites. Using such a tool to check a single faulty endpoint would be an overkill.

So what should you choose for ad hoc testing instead? There are simplified GUI tools, e.g. Postman, and many developers are happy with them. But if you are after ultimate performance and love command line, there is a better option - curl. In this post I’ll show how to check RESTful endpoints using curl with a lot of examples.

curl

Many developers use curl just to fetch remote files and even do not suspect what a powerful tool they run. curl can transfer data from or to a server, using FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET and TFTP protocols. It has lots of options for almost every edge case.

For testing RESTful endpoints, however, you don’t need all that power. Basically, HTTP and HTTPS is all you need. I’ll use HTTP to simplify examples, but feel free to experiment with HTTPS if you feel like.

Mac and Linux users should already have curl installed out of the box. Windows users can downloaded it from here.

Simple service in Go

The best way to learn something new is to play and experiment with it. curl is not an exception, so I’ve rustled up a simple service in Go for curl experiments:

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
)

type RequestSummary struct {
	URL     string
	Method  string
	Headers http.Header
	Params  url.Values
	Auth    *url.Userinfo
	Body    string
}

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		bytes, err := ioutil.ReadAll(r.Body)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		rs := RequestSummary{
			URL:     r.URL.RequestURI(),
			Method:  r.Method,
			Headers: r.Header,
			Params:  r.URL.Query(),
			Auth:    r.URL.User,
			Body:    string(bytes),
		}

		resp, err := json.MarshalIndent(&rs, "", "\t")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Write(resp)
		w.Write([]byte("\n"))
	})

	http.ListenAndServe(":8080", nil)
	fmt.Println("Exiting...")
}

The service is running on localhost:8080 and returns the most interesting pieces of request in json format, so that you can see what is received. Below is an example of what service returns for a GET request:

$ curl -X GET http://localhost:8080/something
{
	"URL": "/something",
	"Method": "GET",
	"Headers": {
		"Accept": [
			"*/*"
		],
		"User-Agent": [
			"curl/7.43.0"
		]
	},
	"Params": {},
	"Auth": null,
	"Body": ""
}

Checking RESTful endpoints

Before jumping in with both feet, let’s first get familiar with some curl options commonly used with all HTTP methods:

-X or –request specifies HTTP method (defaulting to GET), e.g -X POST.

-H or –header sets request header. E.g. -H "Content-Type:application/json" sets content type to application/json.

GET

Most often you’ll need to call get with some parameters:

$ curl -X GET "http://localhost:8080/something?param1=value1&param2=value2"
{
	"URL": "/something?param1=value1\u0026param2=value2",
	"Method": "GET",
	"Headers": {
		"Accept": [
			"*/*"
		],
		"User-Agent": [
			"curl/7.43.0"
		]
	},
	"Params": {
		"param1": [
			"value1"
		],
		"param2": [
			"value2"
		]
	},
	"Auth": null,
	"Body": ""
}

Note that url with parameters needs to be escaped to prevent shell from interpreting &.

If you need to urlencode your data, use -G and –data-urlencode instead:

$ curl -G --data-urlencode "message=hello world" http://localhost:8080/something
{
	"URL": "/something?message=hello%20world",
	"Method": "GET",
	"Headers": {
		"Accept": [
			"*/*"
		],
		"User-Agent": [
			"curl/7.43.0"
		]
	},
	"Params": {
		"message": [
			"hello world"
		]
	},
	"Auth": null,
	"Body": ""
}

–data-urlencode, as it follows from its name, urlencodes your parameter. It’s a POST method’s option, so you have to add -G option, which append all data specified with -d, –data, –data-binary or –data-urlencode to the URL with a ? separator.

POST and PUT

For POST and PUT you often need to pass some data in request body. With -d or –data you can pass data directly in command line or in a file using @filename syntax. In both cases you need to set Content-Type to application/json.

If data is small, it could be convenient to pass it directly in command line:

$ curl -X POST -H "Content-Type:application/json" -d '{"first": "John", "last": "Dow"}' http://localhost:8080/something
{
	"URL": "/something",
	"Method": "POST",
	"Headers": {
		"Accept": [
			"*/*"
		],
		"Content-Length": [
			"32"
		],
		"Content-Type": [
			"application/json"
		],
		"User-Agent": [
			"curl/7.43.0"
		]
	},
	"Params": {},
	"Auth": null,
	"Body": "{\"first\": \"John\", \"last\": \"Dow\"}"
}

In most cases however, it’s preferred to serve data from a file:

$ cat data.json
{
        "first": "John",
        "last": "Dow"
}

$ curl -X PUT -H "Content-Type:application/json" -d @data.json http://localhost:8080/something
{
	"URL": "/something",
	"Method": "PUT",
	"Headers": {
		"Accept": [
			"*/*"
		],
		"Content-Length": [
			"33"
		],
		"Content-Type": [
			"application/json"
		],
		"User-Agent": [
			"curl/7.43.0"
		]
	},
	"Params": {},
	"Auth": null,
	"Body": "{\t\"first\": \"John\",\t\"last\": \"Dow\"}"
}

Note that –data strips out carriage returns and newlines when reading from a file. Use –data-binary if you need your data to be passed intact:

$ curl -X PUT -H "Content-Type:application/json" --data-binary @data.json http://localhost:8080/something
{
	"URL": "/something",
	"Method": "PUT",
	"Headers": {
		"Accept": [
			"*/*"
		],
		"Content-Length": [
			"37"
		],
		"Content-Type": [
			"application/json"
		],
		"User-Agent": [
			"curl/7.43.0"
		]
	},
	"Params": {},
	"Auth": null,
	"Body": "{\n\t\"first\": \"John\",\n\t\"last\": \"Dow\"\n}\n"
}
DELETE

Delete is less often used in practice:

$ curl -X DELETE http://localhost:8080/collection/id
{
	"URL": "/collection/id",
	"Method": "DELETE",
	"Headers": {
		"Accept": [
			"*/*"
		],
		"User-Agent": [
			"curl/7.43.0"
		]
	},
	"Params": {},
	"Auth": null,
	"Body": ""
}

Basically, this is enough for ad hoc testing restful microservices. In the next post I’ll write on more advanced curl options, which help to smooth off the rough edges in day to day usage.

How To Create Health Check For RESTful Microservice In Golang

How To Create Health Check For RESTful Microservice In Golang

Imagine you’ve recently released and deployed to production a cool RESTful microservice you worked on for a while. You heaved a sigh of relief just to hear from Ops team that your service is unstable. You are damn sure that the service should be fine, but you get a feeling that there could be something wrong with services it depends on. What should you do?

Health check will come to your rescue. It is an endpoint in your service returning status of your application including statuses of connections to all external services your service directly depends on. In this post I’ll show how to create a health check for a microservice running on multiple nodes, storing its state in MongoDB and calling Elasticsearch.

If you raised an eyebrow, surprised by why your service should monitor external services… You are right, external services must be monitored independently. In practice, however, some checks may be temporarily down. Nothing is more permanent than the temporary. So it’s a good practice to include your direct dependencies in service status, so you (and Ops) always know what’s broken.

Design

As I alluded earlier, imagine you have a microservice running on multiple nodes, keeping state in MongoDB and calling Elasticsearch. What health check should look like for such a service?

Let’s address the question from different aspects.

Endpoint

An easy one. Let’s follow industry naming convention and call the endpoint /health.

Format

For RESTful service, you should always return HTTP status code 200 and the state as content in JSON format.

Content

This is an interesting one. Response content must reflect health of all critical parts of the service. In our case they are nodes, connection to MongoDB and connection to Elasticsearch. Represented as Golang struct, health status may look like below.

type HealthStatus struct {
	Nodes   map[string]string `json:"nodes"`
	Mongo   string `json:"mongo"`
	Elastic string `json:"elastic"`
}

Implementation

A descriptive way to demonstrate how health check fits in a microservice is to show it together with other modules it collaborates with. A skeleton of my example will have the following modules:

  • main
  • mongo
  • elastic
  • health
main module

main module just sets up the service:

package main

import (
	"encoding/json"
	"github.com/ypitsishin/code-with-yury-examples/healthcheck/elastic"
	"github.com/ypitsishin/code-with-yury-examples/healthcheck/health"
	"github.com/ypitsishin/code-with-yury-examples/healthcheck/mongo"
	"net/http"
)

func main() {
	healthService := health.New([]string{"node1", "node2", "node3"}, mongo.New(), elastic.New())
	http.HandleFunc("/health", statusHandler(healthService))
	http.ListenAndServe("localhost:8080", nil)
}

func statusHandler(healthService health.Service) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		bytes, err := json.MarshalIndent(healthService.Health(), "", "\t")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Write(bytes)
	}
}

Note that health service needs access to both mongo and elastic modules.

mongo and elastic modules

I’ll use rand package to simulate random errors occurring in MongoDB, Elasticsearch and nodes. A simple simulated mongo module is below. elastic module is similar.

package mongo

import (
	"math/rand"
	"errors"
)

type Service interface {
	Health() error
	// Business methods go here
}

func New() Service {
	return &service{}
}

type service struct {
	// Some fields
}

func (s *service) Health() error {
	if rand.Intn(2) > 0 {
		return errors.New("Service unavailable")
	}
	return nil
}
health module

And finally health module itself:

package health

import (
	"github.com/ypitsishin/code-with-yury-examples/healthcheck/mongo"
	"github.com/ypitsishin/code-with-yury-examples/healthcheck/elastic"
	"math/rand"
	"fmt"
)

type HealthStatus struct {
	Nodes   map[string]string `json:"nodes"`
	Mongo   string `json:"mongo"`
	Elastic string `json:"elastic"`
}

type Service interface {
	Health() HealthStatus
}

type service struct {
	nodes   []string
	mongo   mongo.Service
	elastic elastic.Service
}

func New(nodes []string, mongo mongo.Service, elastic elastic.Service) Service {
	return &service{
		nodes: nodes,
		mongo: mongo,
		elastic: elastic,
	}
}

func (s *service) Health() HealthStatus {
	nodesStatus := make(map[string]string)
	for _, n := range s.nodes {
		if rand.Intn(10) > 7 {
			nodesStatus[n] = "Node ERROR: Node not responding"
		} else {
			nodesStatus[n] = "OK"
		}
	}

	mongoStatus := "OK"
	if err := s.mongo.Health(); err != nil {
		mongoStatus = fmt.Sprintf("Mongo ERROR: %s", err)
	}

	elasticStatus := "OK"
	if err := s.elastic.Health(); err != nil {
		elasticStatus = fmt.Sprintf("Elastic ERROR: %s", err)
	}

	return HealthStatus{
		Nodes: nodesStatus,
		Mongo: mongoStatus,
		Elastic: elasticStatus,
	}
}

Note that error messages follow pattern <service> ERROR: <detail>. This is important as health status messages are intended to be consumed by monitoring systems, e.g. Sensu, and should be easy to parse.

Testing

Calling health check via curl

curl localhost:8080/health

outputs

{
	"nodes": {
		"node1": "OK",
		"node2": "OK",
		"node3": "OK"
	},
	"mongo": "Mongo ERROR: Service unavailable",
	"elastic": "OK"
}

Every time you run curl command may result in different output, because errors are randomised.

Unit Testing Golang Code Calling Elasticsearch

Unit Testing Golang Code Calling Elasticsearch

There is an amazing Elasticsearch client for Go and in Working With Elasticsearch I demonstrated with example how to index and search for documents using it. However, if you want to ensure your code will continue working correctly and will not be inadvertently broken during refactoring, you have to cover the code with tests.

In this post I’ll show how to unit test Go code interoperating with Elasticsearch. Keep in mind, however, that the same approach is applicable to unit testing almost any code calling external RESTful API.

A service calling Elasticsearch

Suppose you have a logging service which can fetch n last entries of any application log sorted by time in ascending order. Method GetLog below does exactly that. I provided whole code for a production ready service, to show you how it usually looks in practice.

package logging

import (
	"gopkg.in/olivere/elastic.v3"
	"reflect"
)

type Service interface {
	GetLog(app string, lines int) ([]string, error)
}

func NewService(url string) (Service, error) {
	client, err := elastic.NewSimpleClient(elastic.SetURL(url))
	if err != nil {
		return nil, err
	}
	return &service{elasticClient: client}, nil
}

type service struct {
	elasticClient *elastic.Client
}

type Log struct {
	Message string `json:"message"`
}

// GetLog returns limited tail of log sorted by time in ascending order
func (s *service) GetLog(app string, limit int) ([]string, error) {
	termQuery := elastic.NewTermQuery("app", app)

	res, err := s.elasticClient.Search("_all").
		Query(termQuery).
		Sort("@timestamp", false).
		Size(limit).
		Do()

	if err != nil {
		return nil, err
	}

	msgNum := len(res.Hits.Hits)
	if msgNum == 0 {
		return []string{}, nil
	}

	messages := make([]string, msgNum, msgNum)

	var l Log
	for i, item := range res.Each(reflect.TypeOf(l)) {
		l := item.(Log)
		messages[i] = l.Message
	}

	// Reversing messages
	for i := 0; i < msgNum/2; i++ {
		messages[i], messages[msgNum-(i+1)] = messages[msgNum-(i+1)], messages[i]
	}

	return messages, nil
}

Note that log is fetched from Elasticsearch in descending order first and then reversed back before returning result to caller of logging service. This trick is needed to ensure we get tail of the log capped by limit.

Unit testing the service

In general, the simplest way to unit test code calling external service via client is to mock the client. Unfortunately, elastic.Client is implemented as struct, which makes mocking it a not trivial issue.

Much easier in this case would be to go deeper and mock Elasticsearch RESTful API. One of options is to run httptest.Server which will serve a hardcoded response, where the response is a copy of what Elasticsearch fetches for some predefined request:

package logging

import (
	"github.com/stretchr/testify/assert"
	"gopkg.in/olivere/elastic.v3"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestLog(t *testing.T) {
	handler := http.NotFound
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		handler(w, r)
	}))
	defer ts.Close()

	handler = func(w http.ResponseWriter, r *http.Request) {
		resp := `{
			  "took" : 122,
			  "timed_out" : false,
			  "_shards" : {
			    "total" : 6,
			    "successful" : 5,
			    "failed" : 1,
			    "failures" : [ {
			      "shard" : 0,
			      "index" : ".kibana",
			      "node" : "jucBX9QkQIini9dLG9tZIw",
			      "reason" : {
				"type" : "search_parse_exception",
				"reason" : "No mapping found for [offset] in order to sort on"
			      }
			    } ]
			  },
			  "hits" : {
			    "total" : 10,
			    "max_score" : null,
			    "hits" : [ {
			      "_index" : "logstash-2016.07.25",
			      "_type" : "log",
			      "_id" : "AVYkNv542Gim_t2htKPU",
			      "_score" : null,
			      "_source" : {
				"message" : "Alice message 10",
				"@version" : "1",
				"@timestamp" : "2016-07-25T22:39:55.760Z",
				"source" : "/Users/yury/logs/alice.log",
				"offset" : 144,
				"type" : "log",
				"input_type" : "log",
				"count" : 1,
				"fields" : null,
				"beat" : {
				  "hostname" : "Yurys-MacBook-Pro.local",
				  "name" : "Yurys-MacBook-Pro.local"
				},
				"host" : "Yurys-MacBook-Pro.local",
				"tags" : [ "beats_input_codec_plain_applied" ],
				"app" : "alice"
			      },
			      "sort" : [ 144 ]
			    }, {
			      "_index" : "logstash-2016.07.25",
			      "_type" : "log",
			      "_id" : "AVYkNv542Gim_t2htKPT",
			      "_score" : null,
			      "_source" : {
				"message" : "Alice message 9",
				"@version" : "1",
				"@timestamp" : "2016-07-25T22:39:55.760Z",
				"source" : "/Users/yury/logs/alice.log",
				"offset" : 128,
				"input_type" : "log",
				"count" : 1,
				"beat" : {
				  "hostname" : "Yurys-MacBook-Pro.local",
				  "name" : "Yurys-MacBook-Pro.local"
				},
				"type" : "log",
				"fields" : null,
				"host" : "Yurys-MacBook-Pro.local",
				"tags" : [ "beats_input_codec_plain_applied" ],
				"app" : "alice"
			      },
			      "sort" : [ 128 ]
			    }, {
			      "_index" : "logstash-2016.07.25",
			      "_type" : "log",
			      "_id" : "AVYkNv542Gim_t2htKPR",
			      "_score" : null,
			      "_source" : {
				"message" : "Alice message 8",
				"@version" : "1",
				"@timestamp" : "2016-07-25T22:39:55.760Z",
				"type" : "log",
				"input_type" : "log",
				"source" : "/Users/yury/logs/alice.log",
				"count" : 1,
				"fields" : null,
				"beat" : {
				  "hostname" : "Yurys-MacBook-Pro.local",
				  "name" : "Yurys-MacBook-Pro.local"
				},
				"offset" : 112,
				"host" : "Yurys-MacBook-Pro.local",
				"tags" : [ "beats_input_codec_plain_applied" ],
				"app" : "alice"
			      },
			      "sort" : [ 112 ]
			    } ]
			  }
			}`

		w.Write([]byte(resp))
	}

	s, err := MockService(ts.URL)
	assert.NoError(t, err)

	expectedMessages := []string{
		"Alice message 8",
		"Alice message 9",
		"Alice message 10",
	}
	actualMessages, err := s.GetLog("app", 3)
	assert.NoError(t, err)
	assert.Equal(t, expectedMessages, actualMessages)
}

func MockService(url string) (Service, error) {
	client, err := elastic.NewSimpleClient(elastic.SetURL(url))
	if err != nil {
		return nil, err
	}
	return &service{elasticClient: client}, nil
}

Instead of hardcoding response, you can read it from a file.

By the way, if you are not sure why resp is casted to []byte in w.Write([]byte(resp)), have a look at How To Correctly Serialize JSON String In Golang, which describes the issue in details.

A couple of words on integration testing

Even though the post is devoted to unit testing Go code calling external service, I must admit that a better way to test such code would be via integration testing. Integration tests are executed on the whole system with all its components up and running. This makes the system as close to production as possible and testing the whole system provides much higher quality guarantees.

In practice, however, integration tests are usually more difficult to implement and require much more time to run. As result, integration tests are written less often than they should be.

The bottom line

When unit testing Go code calling external RESTful service via Go client, the easiest approach is to mock the client. If Go client is represented by struct (instead of interface), you can mock RESTful API itself and make the mock to serve pre-recorded real responses.

How To Correctly Serialize JSON String In Golang

How To Correctly Serialize JSON String In Golang

json is one of the most wildly used Go packages. It is simple and, what is more important, very intuitive. So what could be easier than marshalling a string with JSON and unmarshalling it to struct? If you believe (as I did) that the issue is trivial and json.Marshal does the job, read on.

What’s wrong with json.Marshal?

It’s easier to demonstrate on example. Let’s write a simple program which serializes JSON string to bytes and deserializes the bytes into matching struct:

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    FirstName string `json:"firstName"`
    LastName  string `json:"lastName"`
}

func main() {
    in := `{"firstName":"John","lastName":"Dow"}`

    bytes, err := json.Marshal(in)
    if err != nil {
        panic(err)
    }

    var p Person
    err = json.Unmarshal(bytes, &p)
    if err != nil {
        panic(err)
    }

    fmt.Printf("%+v", p)
}

When you run the program it will panic:

panic: json: cannot unmarshal string into Go value of type main.Person

goroutine 1 [running]:
panic(0x1064c0, 0xc820014240)
    /usr/local/go/src/runtime/panic.go:481 +0x3e6
main.main()
    /Users/yury/projects/go/src/exp/main.go:24 +0x194

This is unexpected. Let’s make sure that JSON string matches Person struct:

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    FirstName string `json:"firstName"`
    LastName  string `json:"lastName"`
}

func main() {
    bytes, err := json.Marshal(Person{
        FirstName: "John",
        LastName:  "Dow",
    })
    if err != nil {
        panic(err)
    }

    fmt.Println(string(bytes))
}

Which outputs:

{"firstName":"John","lastName":"Dow"}

Serialized struct is identical to our JSON string.

What’s going on?

Let’s compare serialized JSON string with result of struct serialization:

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    FirstName string `json:"firstName"`
    LastName  string `json:"lastName"`
}

func main() {
    bytes, err := json.Marshal(Person{
        FirstName: "John",
        LastName:  "Dow",
    })
    if err != nil {
        panic(err)
    }

    fmt.Println(string(bytes))

    in := `{"firstName":"John","lastName":"Dow"}`

    bytes, err = json.Marshal(in)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(bytes))
}

The output reveals the truth:

{"firstName":"John","lastName":"Dow"}
"{\"firstName\":\"John\",\"lastName\":\"Dow\"}"

Can you see escaped double quotes? This is the crux of the problem. json.Marshal escapes string while serializing it.

Skipping escaping

json package includes a solution for the issue. It has RawMessage type, which Marshalls and Unmarshals without escaping. So if you need to serialize JSON string which you need to deserialize later into struct, do it with json.RawMessage:

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    FirstName string `json:"firstName"`
    LastName  string `json:"lastName"`
}

func main() {
    in := `{"firstName":"John","lastName":"Dow"}`

    rawIn := json.RawMessage(in)
    bytes, err := rawIn.MarshalJSON()
    if err != nil {
        panic(err)
    }

    var p Person
    err = json.Unmarshal(bytes, &p)
    if err != nil {
        panic(err)
    }

    fmt.Printf("%+v", p)
}

Output as expected:

{FirstName:John LastName:Dow}
Is there a simpler way?

Yes. json.RawMessage is just an alias to []byte. So what you really need is to convert string into []byte:

package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	FirstName string `json:"firstName"`
	LastName  string `json:"lastName"`
}

func main() {
	in := `{"firstName":"John","lastName":"Dow"}`
	bytes := []byte(in)

	var p Person
	err := json.Unmarshal(bytes, &p)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%+v", p)
}

Output is the same:

{FirstName:John LastName:Dow}
Collecting Logs In Elasticsearch With Filebeat and Logstash

Collecting Logs In Elasticsearch With Filebeat and Logstash

You are lucky if you’ve never been involved into confrontation between devops and developers in your career on any side. In this post I’ll show a solution to an issue which is often under dispute - access to application logs in production.

The issue at hand

Imagine you are a devops responsible for running company applications in production. Applications are supported by developers who obviously don’t have access to production environment and, therefore, to production logs.

Imagine that each server runs multiple applications, and applications store logs in /var/log/apps. A server with two running applications will have log layout:

$ tree /var/log/apps
/var/log/apps
├── alice.log
└── bob.log

The problem: How to let developers access their production logs efficiently?

A solution

Feeling developers’ pain (or getting pissed off by regular “favours”), you decided to collect all application logs in Elasticsearch, where every developer can search for them. The simplest implementation would be to setup Elasticsearch and configure Filebeat to forward application logs directly to Elasticsearch.

Elasticsearch

I’ve described in details a quick intro to Elasticsearch and how to install it in my previous post. So have a look there if you don’t know how to do it.

Filebeat

Filebeat, which replaced Logstash-Forwarder some time ago, is installed on your servers as an agent. It monitors log files and can forward them directly to Elasticsearch for indexing.

Filebeat configuration which solves the problem via forwarding logs directly to Elasticsearch could be as simple as:

filebeat:
  prospectors:
    -
      paths:
        - /var/log/apps/*.log
      input_type: log

output:
  elasticsearch:
    hosts: ["localhost:9200"]

It’ll work. Developers will be able to search for log using source field, which is added by Filebeat and contains log file path.

Note that I used localhost with default port and bare minimum of settings.

If you’re paranoid about security, you have probably risen eyebrows already. Developers shouldn’t know about logs location. But this is a different story.

I bet developers will get pissed off very soon with this solution. They have to do term search with full log file path or they risk receiving non-related records from logs with similar partial name. The problem is aggravated if you run applications inside Docker containers managed by Mesos or Kubernetes.

A better solution

A better solution would be to introduce one more step. Instead of sending logs directly to Elasticsearch, Filebeat should send them to Logstash first. Logstash will enrich logs with metadata to enable simple precise search and then will forward enriched logs to Elasticsearch for indexing.

Logstash

Logstash is the best open source data collection engine with real-time pipelining capabilities. Logstash can cleanse logs, create new fields by extracting values from log message and other fields using very powerful extensible expression language and a lot more.

Introduction of a new app field, bearing application name extracted from source field, would be enough to solve the problem.

Final configuration

Filebeat configuration will change to

filebeat:
  prospectors:
    -
      paths:
        - /var/log/apps/*.log
      input_type: log

output:
  logstash:
    hosts: ["localhost:5044"]

and Logstash configuration will look like

input {
    beats {
        port => "5044"
    }
}

filter {
    grok {
        match => { "source" => "%{GREEDYDATA}/%{GREEDYDATA:app}.log" }
    }
}

output {
    elasticsearch {
        hosts => ["localhost:9200"]
    }
}

Both configuration files are self-explanatory. The only snippet deserving explanation is:

grok {
    match => { "source" => "%{GREEDYDATA}/%{GREEDYDATA:app}.log" }
}

If source field has value “/var/log/apps/alice.log”, the match will extract word alice and set it as value of newly created field app.

The bottom line

The final solution is way better. Developers can run exact term queries on app field, e.g:

$ curl http://localhost:9200/_all/_search?q=app:bob&sort=@tymestamp:asc&sort=offset:asc&fields=message&pretty | grep message

with output

        "message" : [ "Bob message 1" ]
        "message" : [ "Bob message 2" ]
        "message" : [ "Bob message 3" ]
        "message" : [ "Bob message 4" ]
        "message" : [ "Bob message 5" ]
        "message" : [ "Bob message 6" ]
        "message" : [ "Bob message 7" ]
        "message" : [ "Bob message 8" ]
        "message" : [ "Bob message 9" ]
        "message" : [ "Bob message 10" ]

I hope you guested it was a joke. Install Kibana for log browsing to make developers ecstatic.

Working With Elasticsearch in Go

Working With Elasticsearch in Go

Elasticsearch is one of the most popular highly scalable full-text search engines on the market. It is based on Lucene engine and allows you to store, search, and analyze big volumes of data quickly and in near real time. It has rich REST API and clients for most popular programming languages including Go. In this post I’ll demonstrate on example how to index and search for application logs using Go.

Installing Elasticsearch

If you haven’t set it up already, installation of Elasticsearch is as easy as downloading it (from here) and running the executable file. I installed it on localhost and will use it in my examples.

When installation is over, let’s test that Elasticsearch is up and running:

curl http://localhost:9200

The response should be similar to:

{
  "name" : "Specialist",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.3.4",
    "build_hash" : "e455fd0c13dceca8dbbdbb1665d068ae55dabe3f",
    "build_timestamp" : "2016-06-30T11:24:31Z",
    "build_snapshot" : false,
    "lucene_version" : "5.5.0"
  },
  "tagline" : "You Know, for Search"
}
Quick intro to Elasticsearch

In nutshell, Elasticsearh is a distributed document storage. As a storage it allows you to execute CRUD (create, read, update, delete) operations on documents, but, what is more important, Elasticsearh lets you efficiently search for documents it stores.

So what is a document? Probably you’ve already heard that Elasticsearch documents have fields and are represented by json objects. This is correct, but only half-truth. Under the hood Elasticsearch flattens json objects, so a document is internally represented by a map from fields to their values. For example, the json document below

{
  "name" : "Zach",
  "car" : [
    {
      "make" : "Saturn",
      "model" : "SL"
    },
    {
      "make" : "Subaru",
      "model" : "Imprezza"
    }
  ]
}

will be represented internally by the following mapping

{
  "name" : "Zach",
  "car.make" : ["Saturn", "Subaru"]
  "car.model" : ["SL", "Imprezza"]
}

All document fields have type. Elasticsearch supports simple data types like string, numeric, date, boolean, binary as well as complex data types. String type is special though, because string fields can be analyzed. When a field is analyzed, Elasticsearch will allow you to find a document not only by the whole string value, but by a part of it. E.g, if you index the following document

{
  "name" : "Zach Morrison",
  "company" : "Google"
}

and have configured name field to be analyzed, then you can find this document by searching for "name" : "Morrison" or "name" : "Zack".

Elasticsearch stores documents in an index, which is implemented as inverted index. Simply speaking, inverted index is a data structure representing a map from field value to collection of documents having the field with that value. There are usually multiple indices in Elasticsearch, and similar documents are stored in the same index. E.g. a booking app could store hotels in hotels index and plane flights in flights one.

Example of working with Elasticsearch in Go

In an example below, I’ll show how Elasticsearch can be used for storing and searching application logs, which is one of the most popular Elasticsearch use cases. In the example we will create an index, store some log messages in it and find the messages using Query API.

We will utilise Elasticsearch Go client, which is Elasticsearch client written in Go.

package main

import (
	"errors"
	"fmt"
	"gopkg.in/olivere/elastic.v3"
	"reflect"
	"time"
)

const (
	indexName    = "applications"
	docType      = "log"
	appName      = "myApp"
	indexMapping = `{
						"mappings" : {
							"log" : {
								"properties" : {
									"app" : { "type" : "string", "index" : "not_analyzed" },
									"message" : { "type" : "string", "index" : "not_analyzed" },
									"time" : { "type" : "date" }
								}
							}
						}
					}`
)

type Log struct {
	App     string    `json:"app"`
	Message string    `json:"message"`
	Time    time.Time `json:"time"`
}

func main() {
	client, err := elastic.NewClient(elastic.SetURL("http://localhost:9200"))
	if err != nil {
		panic(err)
	}

	err = createIndexWithLogsIfDoesNotExist(client)
	if err != nil {
		panic(err)
	}

	err = findAndPrintAppLogs(client)
	if err != nil {
		panic(err)
	}
}

func createIndexWithLogsIfDoesNotExist(client *elastic.Client) error {
	exists, err := client.IndexExists(indexName).Do()
	if err != nil {
		return err
	}

	if exists {
		return nil
	}

	res, err := client.CreateIndex(indexName).
		Body(indexMapping).
		Do()

	if err != nil {
		return err
	}
	if !res.Acknowledged {
		return errors.New("CreateIndex was not acknowledged. Check that timeout value is correct.")
	}

	return addLogsToIndex(client)
}

func addLogsToIndex(client *elastic.Client) error {
	for i := 0; i < 10; i++ {
		l := Log{
			App:     "myApp",
			Message: fmt.Sprintf("message %d", i),
			Time:    time.Now(),
		}

		_, err := client.Index().
			Index(indexName).
			Type(docType).
			BodyJson(l).
			Do()

		if err != nil {
			return err
		}
	}

	return nil
}

func findAndPrintAppLogs(client *elastic.Client) error {
	termQuery := elastic.NewTermQuery("app", appName)

	res, err := client.Search(indexName).
		Index(indexName).
		Query(termQuery).
		Sort("time", true).
		Do()

	if err != nil {
		return err
	}

	fmt.Println("Logs found:")
	var l Log
	for _, item := range res.Each(reflect.TypeOf(l)) {
		l := item.(Log)
		fmt.Printf("time: %s message: %s\n", l.Time, l.Message)
	}

	return nil
}

Although the code is self-explanatory, a couple of notes could be worthwhile.

First, look at indexName, docType and indexMapping constants. Elasticsearch index allows you to specify a document schema, known as type, where you may declare document fields including their types and, for string field, if it is analyzed or not. In our example we create index named applications, which will store documents of single type named log having three fields: app, message and time. Both string fields declared not_analyzed.

Second, pay attention to the following snippet:

termQuery := elastic.NewTermQuery("app", appName)

Note that we use TermQuery to search for logs. TermQuery requires exact match of the term. In our case, this will require field app of log documents to exactly match our application name to guarantee that returned documents belong to our application, not an application with similar name.

Finally, when you run the code, the output should be similar to:

Logs found:
time: 2016-07-16 16:38:25.128233427 +1000 AEST message: message 0
time: 2016-07-16 16:38:25.152318895 +1000 AEST message: message 1
time: 2016-07-16 16:38:25.156900546 +1000 AEST message: message 2
time: 2016-07-16 16:38:25.159455721 +1000 AEST message: message 3
time: 2016-07-16 16:38:25.164298397 +1000 AEST message: message 4
time: 2016-07-16 16:38:25.169695943 +1000 AEST message: message 5
time: 2016-07-16 16:38:25.172610227 +1000 AEST message: message 6
time: 2016-07-16 16:38:25.175208635 +1000 AEST message: message 7
time: 2016-07-16 16:38:25.181457148 +1000 AEST message: message 8
time: 2016-07-16 16:38:25.183462045 +1000 AEST message: message 9
Example Of Using Templates In Golang

Example Of Using Templates In Golang

Almost every programming language has a library implementing templating. In epoch of server side MVC dominance, templating was so important that it could determine language success or failure. Nowadays, however, when single page applications get momentum, templates are used only occasionally.

The niche where templates are still absolutely indispensable is email content generation. In this post I will demonstrate on an example how to use Golang template package to generate email content based on a template defined in a file. I’ll keep example as simple as possible, but beefy enough to show most commonly used features of the package.

Go code

Below is Go code for the example:

package main

import (
    "time"
    "html/template"
    "os"
    "fmt"
)

type Account struct {
    FirstName string
    LastName  string
}

type Purchase struct {
    Date          time.Time
    Description   string
    AmountInCents int
}

type Statement struct {
    FromDate  time.Time
    ToDate    time.Time
    Account   Account
    Purchases []Purchase
}

func main() {
    fmap := template.FuncMap{
        "formatAsDollars": formatAsDollars,
        "formatAsDate": formatAsDate,
        "urgentNote": urgentNote,
    }
    t := template.Must(template.New("email.tmpl").Funcs(fmap).ParseFiles("email.tmpl"))
    err := t.Execute(os.Stdout, createMockStatement())
    if err != nil {
        panic(err)
    }
}

func formatAsDollars(valueInCents int) (string, error) {
    dollars := valueInCents / 100
    cents := valueInCents % 100
    return fmt.Sprintf("$%d.%2d", dollars, cents), nil
}

func formatAsDate(t time.Time) string {
    year, month, day := t.Date()
    return fmt.Sprintf("%d/%d/%d", day, month, year)
}

func urgentNote(acc Account) string {
    return fmt.Sprintf("You have earned 100 VIP points that can be used for purchases")
}

func createMockStatement() Statement {
    return Statement{
        FromDate: time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC),
        ToDate: time.Date(2016, 2, 1, 0, 0, 0, 0, time.UTC),
        Account: Account{
            FirstName: "John",
            LastName: "Dow",
        },
        Purchases: []Purchase {
            Purchase{
                Date: time.Date(2016, 1, 3, 0, 0, 0, 0, time.UTC),
                Description: "Shovel",
                AmountInCents: 2326,
            },
            Purchase{
                Date: time.Date(2016, 1, 8, 0, 0, 0, 0, time.UTC),
                Description: "Staple remover",
                AmountInCents: 5432,
            },
        },
    }
}

The most important parts of Go code are data structures and main function. Other functions are less important and serve the only purpose to show different ways to call a function from a template.

Let’s start with data structures representing a statement. Note that Statement includes an Account and a slice of Purchase.

Next let’s zoom in on main function, where template creation and execution occurs. This method is a bit complicated, so we’ll study it statement by statement.

    fmap := template.FuncMap{
        "formatAsDollars": formatAsDollars,
        "formatAsDate": formatAsDate,
        "urgentNote": urgentNote,
    }

Here we create a FuncMap, which is basically a map of function names to functions. When passed to a template, FuncMap allows it to call functions defined in the map.

Next statement is really complex, so I’ve split it into 2 pieces.

template.New("email.tmpl").Funcs(fmap).ParseFiles("email.tmpl")

This snippet creates a new template with name email.tmpl and feeds it with the FuncMap created earlier. Then ParseFiles reads template from file email.tmpl and parses the template.

Note that template name and file name are the same. It’s a trick to to tell ParseFiles not to create a new template, but reuse the one created with New function instead.

t := template.Must()

ParseFiles returns (*Template, error), and Must function is a helper method which ensures that template is correct. Basically, Must panics if returned error is not nil.

err := t.Execute(os.Stdout, createMockStatement())

The final piece applies template on a mock statement with output set to Stdout.

Template file

Note that template file email.tmpl below requires Go 1.6 or later.

{{with .Account -}}
Dear {{.FirstName}} {{.LastName}},
{{- end}}

Below are your account statement details for period from {{.FromDate | formatAsDate}} to {{.ToDate | formatAsDate}}.

{{if .Purchases -}}
    Your purchases:
    {{- range .Purchases }}
        {{ .Date | formatAsDate}} {{ printf "%-20s" .Description }} {{.AmountInCents | formatAsDollars -}}
    {{- end}}
{{- else}}
You didn't make any purchases during the period.
{{- end}}

{{$note := urgentNote .Account -}}
{{if $note -}}
Note: {{$note}}
{{- end}}

Best Wishes,
Customer Service

As you can see, Go has chosen double braces (“{{” and “}}”) to delimit data evaluation and control structures (known as actions) in templates. Some double braces have - attached, which tells Go to remove all spaces on the corresponding side.

Let’s consider templating features one by one.

Current context

While rendering a template, Go uses a concept of current context. Current context is denoted by . and it’s initial value is set to second parameter of Execute method. So .Account in our example references Account field of mock template created with createMockStatement().

Pipeline

Pipeline is a unique Go templating feature, which allows to declare expressions that can be executed in a manner similar to shell pipeline. Formally, a pipeline is a chained sequence of commands separated by | symbol. A command can be a simple value or a function call. The result of each command is passed as the last argument to the following command. The output of the final command in the pipeline is the value of the whole pipeline.

A command is allowed to return one or two values, the second of which must be of error type. If command returns two values and the second value evaluates to non-nil, execution terminates and the error is returned to the caller of Execute.

In our example, .FromDate | formatAsDate pipeline passes .FromDate as argument to formatAsDate function and the whole pipeline evaluates to result of the function.

with action
{{with pipeline}} T {{end}}

Sometime it’s convenient to change current context and with does exactly that. In example we change current context to Account field, so that .FirstName and .LastName could be accessed directly (instead of .Account.FirstName and .Account.LastName).

if action
{{if pipeline}} T1 {{else}} T2 {{end}}

if action behaves similar to if statement in Go. The false values are false, 0, any string of length zero.

range action
{{range pipeline}} T {{end}}

range action (similar to range Go statement) loops through elements of the result of pipeline, and makes an element the new current context inside the loop.

Using variables
{{$variable := pipeline}}

You can define a variable inside a template. The syntax is similar to Go assignment.

Example output

After we examined all bits and pieces of the example, let’s run it. Below is the output to be generated.

Dear John Dow,

Below are your account statement details for period from 1/1/2016 to 1/2/2016.

Your purchases:
        3/1/2016 Shovel               $23.26
        8/1/2016 Staple remover       $54.32

Note: You have earned 100 VIP points that can be used for purchases

Best Wishes,
Customer Service