Skip to content

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

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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions