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.300.
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.
Platform Updates
Docker Images
- The Unit JSC11 image is now based on
eclipse-temurin
instead ofopenjdk
- Go version bump: 1.18 → 1.19
- Perl version bump: 5.34 → 5.36
Wbr, Timo & the Unit team