Unit 1.28.0 Released§
We are happy to announce Unit 1.28! This release sets the first milestone for observability:
- It is now possible to get basic information about connections, requests, and other per-application metrics
- All this is now available via our powerful RESTful API
In addition, we introduce new variables and the ability to use them to customize the access log format. Besides the long-awaited statistics and logging use cases, we also present:
- Enhanced forward header handling with new configuration syntax and X-Forwarded-Proto support
- Support for abstract UNIX domain sockets in listeners on Linux-like systems
- Fixes for several community-reported bugs
Metrics and Statistics§
With 1.28, the Unit API has a new endpoint available; the /status
endpoint is exposed at the root level, as with the /config
and
/certificates
endpoints:
curl --unix-socket /var/run/control.unit.sock http://localhost
{
"config": {
"listeners": {
},
"applications": {
}
},
"status": {
"connections": {
"accepted": 0,
"active": 0,
"idle": 0,
"closed": 0
},
"requests": {
"total": 0
},
"applications": {}
}
}
The status
object contains three nested objects:
- The
connections
object provides detailed information about the client connections to the Unit instance or, specifically, to its listeners. Here,accepted
andclosed
are total values accumulated over the instance’s lifetime; restarting Unit resets the total values. - In contrast,
active
andidle
are spot values representing the number of active or idle requests at one of the listeners that Unit exposes.
The requests
object holds the total number of requests to all exposed
listeners since the last restart.
Note
Both connections
and requests
count requests to Unit’s
listeners, NOT the config API itself.
- The
applications
section follows the/config/applications
tree in the API; again, there’s no special setup required because Unit automatically maintains per-app metrics for all applications in/config/applications
, and the apps’ names identify them respectively.
Consider the following applications configuration as an example:
{
"my-app":{
"type": "external",
"working_directory": "/www/chat",
"executable": "bin/chat_app",
"processes":{
"max": 10,
"spare": 5,
"idle_timeout": 20
}
}
}
The interesting part is the processes
configuration. We defined a
maximum of 10 and a spare number of 5 processes; the idle_timeout
is 20
seconds. After a couple of requests, let’s look at the app statistics:
{
"my-app":{
"processes":{
"running": 9,
"starting": 0,
"idle": 2
},
"requests":{
"active": 9
}
}
}
Knowing the process configuration of my-app
, this is quite easy to
understand. Currently, there are 9 out of 10 total processes running, while 0
are currently starting. The two idles are inactive app processes that have not
reached the idle_timeout
yet; these will be removed when the configured
timeout of 20 seconds elapses, so the number of running processes will drop to
7.
But what would the stats look like if the app gets no more requests or isn’t able to handle the incoming traffic with the minimum number of configured processes?
{
"my-app":{
"processes":{
"running": 5,
"starting": 0,
"idle": 0
},
"requests":{
"active": 1
}
}
}
Correct! The number of currently running processes matches the spare
configuration defined in applications/my-app/processes/spare
.
So, with Unit 1.28, you now can see your basic workload and process statistics for the Unit instance itself as well as individual applications. This is but a first, very important step to increased visibility for us.
More Variables and Access Log Customization§
Another noteworthy development is all about variables. First, 1.28.0 adds a few, namely:
$remote_addr, $time_local, $request_line, $status,
$body_bytes_sent, $header_referer, $header_user_agent
Most are self-explanatory but note that some are populated from the
response, such as $status
or $body_bytes_sent
. That comes in
handy with another new feature, the custom access log format:
{
"access_log":{
"path":"/var/log/unit/access.log",
"format":"$remote_addr - - [$time_local] \"$request_line\" $status $body_bytes_sent \"$header_referer\" \"$header_user_agent\""
}
}
The access_log
option can be set to an object that defines both the log
path and the entry structure, so you can go beyond the combined log format and
choose XML or JSON for your log if you like.
Finally, request arguments, cookies, and headers are now also exposed as
dynamic variables: for instance, a query string of Type=car&Color=red
results in two argument variables, $arg_Type
and $arg_Color
.
X-Forwarded-* Headers Replacement§
When passing an incoming request to a Unit language module, we build an internal context to store all information related to the request, including the client’s IP and the protocol used (plain-text HTTP or encrypted HTTPS). When there is no caching layer or reverse proxy in front of Unit, this information stays correct (as it’s included in the request), but that changes when a proxy or a cache stands between the client and Unit.
In that case, the client’s IP will always be the IP address of the proxy/cache
server, and the same applies to the protocol. If the connection from the client
to this server uses HTTPS, but it’s HTTP all the way to Unit, we have to tell
the app: “Hey, the protocol we use to talk to the client is actually HTTPS.
Keep this in mind when building links and routes internally.” That’s where the
X-Forwarded-*
header fields come into play.
To extend Unit’s capabilities, we’ve added support for protocol replacement in version 1.28; now you can configure client IPs and protocol replacement in your listeners’ configuration:
{
"listeners":{
"*:80":{
"pass":"routes/my-app",
"forwarded":{
"client_ip":"X-Forwarded-For",
"protocol":"X-Forwarded-Proto",
"recursive":false,
"source":[
"198.51.100.1-198.51.100.254",
"!198.51.100.128/26",
"203.0.113.195"
]
}
}
},
"routes":{
"my-app":[
{
"action":{
"return":200
}
}
]
},
"applications":{}
}
The configuration above shows the new syntax to configure the replacement;
the old client_ip
syntax will still work but is now deprecated and will
be removed in a future release (no sooner than version 1.30).
We have wrapped client_ip
and protocol
in a new object, while
the recursive
and source
options stay the same; the IPs in
source
are now valid for all replacements in forwarded
.
Another use case for header replacement was prompted by a community-reported issue; now, we have enhanced the support for header replacement in combination with UNIX domain sockets:
{
"listeners":{
"unix:@socket":{
"pass":"routes/my-app",
"forwarded":{
"client_ip":"X-Forwarded-For",
"protocol":"X-Forwarded-Proto",
"recursive":false,
"source":[
"unix",
"198.51.100.1"
]
}
}
},
"routes":{
"my-app":[
{
"action":{
"return":200
}
}
]
},
"applications":{}
}
The source
can include unix
to trigger replacement if the
request was made via a socket, like this:
curl -H "X-Forwarded-For: 192.168.10.100" --abtract-unix-socket socket http://localhost
Are you intrigued by the whole socket listener thing here? Read on!
Abstract UNIX Domain Sockets§
To put it simply, using traditional UNIX sockets with Unit listeners has a few trade-offs that we weren’t ready to accept. Still, there’s a viable option for Linux-like systems, namely, the abstract UNIX sockets! They aren’t tied to the file system, so they don’t carry the overhead of handling the socket files. In turn, this places them quite nicely for use with Unit listeners, so here we are:
{
"listeners": {
"unix:@socket": {
"pass": "routes/sockets"
},
"unix:@/test/123": {
"pass": "routes/sockets"
}
},
"routes": {
"sockets": [
{
"action": {
"return": 200
}
}
]
},
"applications": {}
}
Unlike file-based UNIX sockets, abstract sockets are automatically cleaned up by the Linux kernel when nobody is using them. If you find yourself with untidy UNIX sockets on the filesystem then give abstract sockets a try, but note that this is a Linux-only feature (does not work on BSD systems).
Full Changelog§
Changes with Unit 1.28.0 13 Sep 2022
*) Change: increased the applications' startup timeout.
*) Change: disallowed abstract Unix domain socket syntax in non-Linux
systems.
*) Feature: basic statistics API.
*) Feature: customizable access log format.
*) Feature: more HTTP variables support.
*) Feature: forwarded header to replace client address and protocol.
*) Feature: ability to get dynamic variables.
*) Feature: support for abstract Unix sockets.
*) Feature: support for Unix sockets in address matching.
*) Feature: the $dollar variable translates to a literal "$" during
variable substitution.
*) Bugfix: router process could crash if index file didn't contain an
extension.
*) Bugfix: force SCRIPT_NAME in Ruby to always be an empty string.
*) Bugfix: when isolated PID numbers reach the prototype process host
PID, the prototype crashed.
*) Bugfix: the Ruby application process could crash on SIGTERM.
*) Bugfix: the Ruby application process could crash on SIGINT.
*) Bugfix: mutex leak in the C API.