Skip to content

New s6 console logging doesn't handle multi-line logs #2103

@shaunco

Description

@shaunco

This s6-log config doesn't properly handle multi-line logs, which breaks any form of structured log collector (datadog, signoz, etc):

exec > >(exec s6-log -b p"api[$$]:" 1 || true) 2>&1

So, we end up with logs like this:

api[523]: No webresource handler loaded.
api[523]: (node:531) NOTE: The AWS SDK for JavaScript (v2) is in maintenance mode.
api[523]:  SDK releases are limited to address critical bug fixes and security issues only.
api[523]:
api[523]: Please migrate your code to use AWS SDK for JavaScript (v3).
api[523]: For more information, check the blog post at https://a.co/cUPnyil
api[523]: Successfully executed standard models.
api[523]: Successfully executed migrations model.
api[523]: Successfully executed tasks model.
api[523]: Successfully executed webresource model.
api[523]: Loading application config
api[523]: Successfully executed balena model.
api[523]: Successfully executed v7 model.
api[523]: Successfully executed v6 model.
api[523]: Async migration execution is enabled
api[523]: Creating superuser account...
api[523]: Superuser already exists!
api[523]: Server listening in development mode on port 80
api[523]: node:internal/process/promises:394
api[523]:     triggerUncaughtException(err, true /* fromPromise */);
api[523]:     ^
api[523]:
api[523]: Error [XMLParserError]: Non-whitespace before first tag.
api[523]: Line: 0
api[523]: Column: 1
api[523]: Char: u
api[523]:     at error (/usr/src/app/node_modules/sax/lib/sax.js:667:10)
api[523]:     at strictFail (/usr/src/app/node_modules/sax/lib/sax.js:693:7)
api[523]:     at beginWhiteSpace (/usr/src/app/node_modules/sax/lib/sax.js:967:7)
api[523]:     at SAXParser.write (/usr/src/app/node_modules/sax/lib/sax.js:1019:11)
api[523]:     at exports.Parser.Parser.parseString (/usr/src/app/node_modules/xml2js/lib/parser.js:337:31)
api[523]:     at Parser.parseString (/usr/src/app/node_modules/xml2js/lib/parser.js:5:59)
api[523]:     at NodeXmlParser.parse (/usr/src/app/node_modules/aws-sdk/lib/xml/node_parser.js:31:10)
api[523]:     at Request.extractError (/usr/src/app/node_modules/aws-sdk/lib/services/s3.js:743:39)
api[523]:     at Request.callListeners (/usr/src/app/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
api[523]:     at Request.emit (/usr/src/app/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
api[523]:     at Request.emit (/usr/src/app/node_modules/aws-sdk/lib/request.js:686:14)
api[523]:     at Request.transition (/usr/src/app/node_modules/aws-sdk/lib/request.js:22:10)
api[523]:     at AcceptorStateMachine.runTo (/usr/src/app/node_modules/aws-sdk/lib/state_machine.js:14:12)
api[523]:     at /usr/src/app/node_modules/aws-sdk/lib/state_machine.js:26:10
api[523]:     at Request.<anonymous> (/usr/src/app/node_modules/aws-sdk/lib/request.js:38:9)
api[523]:     at Request.<anonymous> (/usr/src/app/node_modules/aws-sdk/lib/request.js:688:12) {
api[523]:   code: 'XMLParserError',
api[523]:   retryable: true,
api[523]:   time: 2025-09-10T17:57:17.788Z,
api[523]:   statusCode: 503
api[523]: }
api[523]:
api[523]: Node.js v24.7.0
api[523]: Program node --enable-source-maps --loader @swc-node/register/esm-register index.js exited with code 1
api[523]:
s6-rc: info: service legacy-services: stopping

where everything in that middle code block was actually a single line of log output.

Unfortunately s6-log is line oriented, so the exec pipe has to feed something else like jq or awk first:

  1. jq
    # stdout -> jq-> s6-log -> stdout
    exec > >(jq -R -c --arg app "api" --arg pid "$$" \
      '{ts:(now|todateiso8601), app:$app, pid:($pid|tonumber), stream:"stdout", msg:.}' \
      | s6-log -b -l 0 1) \
      2> >(jq -R -c --arg app "api" --arg pid "$$" \
      '{ts:(now|todateiso8601), app:$app, pid:($pid|tonumber), stream:"stderr", msg:.}' \
      | s6-log -b -l 0 1)
  2. awk
    json_filter() {
      awk -v app="$1" -v pid="$$" -v stream="$2" '
        function esc(s){ gsub(/\\/,"\\\\",s); gsub(/"/,"\\\"",s);
                         gsub(/\r/,"",s); gsub(/\t/,"\\t",s); gsub(/\n/,"\\n",s); return s }
        { line=$0; printf("{\"app\":\"%s\",\"pid\":%d,\"stream\":\"%s\",\"msg\":\"%s\"}\n",
                          app, pid, stream, esc(line)); fflush() }'
    }
    exec > >(json_filter api stdout | s6-log -b -l 0 1) \
        2> >(json_filter api stderr | s6-log -b -l 0 1)

The other alternative is to use something like Pino in open-balena-api, but that would require all the various console.log and other logging calls to be reworked into a pino logger ... great long term, but the above is an easier fix. Additionally, the above works for the -s3 and -vpn projects as well that can't move to something line Pino.

(cc @klutchell )

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions