I have a container with an entrypoint that runs a long-lived service.
ENTRYPOINT /entrypoint.sh
The service outputs logs to stdout and stderr in an unstructured format:
Informational message
An error
I need to wrap these logs in a JSON-structured format in order for them to be machine parsable, but I don’t have control over the binary being run. The output should be like this:
{
"service": "foo",
"msg": "Informational message",
"severity": "stdout",
"t": 1692606427758000000,
}
{
"service": "foo",
"msg": "An error",
"severity": "stderr",
"t": 1692606427772000000,
}
My idea was to create a wrapper entrypoint and then stream the output of the entrypoint into jq to generate the structure I want. I came up with this:
#! /bin/bash
exec /entrypoint.sh | jq -R 'split("n")|{service:"foo", msg:.[0], severity:"stdout"}'
This works, but I want to separately handle stdout and stderr to change the severity
field accordingly.
I tried using redirects but can’t figure out how to make jq ingest from them, my attempt below results in nothing being output:
#! /bin/bash
exec /entrypoint.sh
> >( jq -R 'split("n")|{service:"foo", msg:.[0], severity:"stdout"}' )
2> >( jq -R 'split("n")|{service:"foo", msg:.[0], severity:"stderr"}' )
Any tips to how I can make jq behave how I want?
2
Answers
You redirect stdout to be processed by the first jq. The second jq inherits this redirection. This causes garbage.
Let’s demo this.
Output:
We can fix this by duplication stdout before it’s redirection.
Output:
There’s a second issue. The output of the jq processes can come after the command completes.
This is easily solved by adding a
wait
.I don’t know why you get nothing, but I presume one of these two problems is somehow responsible.
BUT! As nice as it is to put everything in one command, having two programs write to the same handle can’t end well. Fixed:
It seems you need to redirect stderr before stdout.
If you want to see output when you
docker run
, add--tty
. Or usestdbuf -oL
(or betterjq --unbuffered
) beforejq
as suggested by @CharlesDuffyDockerfile ==>
entrypoint.sh ==>
entrypoint2.sh ==>
Run the whole thing with :