Skip to content

Reference ​

Everything the package exposes, in tables.

Commands ​

CommandRecordsReplay
/usr/bin/muxen-can-dataloggerevery frame, BUSMASTER text format, one file at a timeyes
/usr/bin/muxen-csv-dataloggerdecoded fields, CSV, one file per device and frame kindno

muxen-can-datalogger ​

muxen-can-datalogger [OPTION]
ShortLongArgumentDefaultMeaning
-h--help——print the usage block on stdout and exit 0
-v--verbose—0repeatable; only a second -v has any effect
-i--interfacecanXcan0CAN interface to record or replay onto
-r--replaypathnonereplay this capture instead of recording
-l--logFolderpath/var/lib/muxen/dataloggerwhere captures are written
—--maxRecordTimeseconds10800age at which a file rotates
—--maxFileSizeBeforeCompressionbytes10485760size at which a file rotates
—--gzipvalue, ignored—parsed, never consulted
—--no-gzipvalue, ignored—parsed, never consulted

muxen-csv-datalogger ​

muxen-csv-datalogger [OPTION]
ShortLongArgumentDefaultMeaning
-h--help——print the usage block on stdout and exit 0
-v--verbose—0repeatable; only a second -v has any effect
-i--interfacecanXcan0CAN interface to record
-l--logFolderpath/var/lib/muxen/dataloggerwhere captures are written
—--maxRecordTimeseconds10800age at which a file rotates
—--maxFileSizeBeforeCompressionbytes10485760size at which a file rotates
—--gzipvalue, ignored—parsed, never consulted
—--no-gzipvalue, ignored—parsed, never consulted

There is no --replay here, and no --version on either command.

Option parsing, exactly ​

These are the behaviours that surprise people. All of them are properties of how the options are declared.

BehaviourDetail
--gzip / --no-gzip need a valueboth are declared as taking a required argument. Bare, they make the parser fail: usage block, exit 0
--gzip / --no-gzip do nothingthe flag they set is never read. Compression is unconditional
-r and -s are not short forms of the rotation limitsmuxen-csv-datalogger declares them for --maxRecordTime and --maxFileSizeBeforeCompression, but neither letter is in its short-option string. Use the long forms
-r on the raw recorder is --replaydifferent meaning from the letter declared in the decoded recorder
--replay matches by abbreviationthe long option is declared with trailing spaces in its name, so --replay matches as a unique prefix rather than exactly. -r avoids the question
the rotation limits use a plain integer conversiona non-numeric value becomes 0, without an error
--maxRecordTime is clampedto at most 86400 seconds
--maxFileSizeBeforeCompression is clampedto at least 1048576 bytes — on the raw recorder only. The decoded recorder applies no floor
--interface is truncatedto the platform interface-name length
an empty --interface prints the usage blockand exits 0
any unrecognised option prints the usage blockand exits 0

Verbosity ​

LevelEffect
nonethe configuration summary and the next file index
-vthe summary reports verbose = 1; nothing else changes
-vvenables the CAN library's own frame-level output

Startup output ​

Both recorders print their whole configuration before doing anything. Under systemd this lands in the journal.

config: verbose = 0
config: interface = can0
config: replay = (null)
config: logFolder = /var/lib/muxen/datalogger
config: maxRecordTime = 10800
config: maxFileSizeBeforeCompression = 10485760
busmaster: next log file index is 1

The config: replay = line exists only on the raw recorder. The decoded recorder prints csv: next log file index is … once per device and frame kind, as each file is created.

Messages ​

MessageWhere fromWhat follows
config: Ensure logFolder exists: <path>startupexit 0
config: Check logFolder permissions: <path>startupexit 0
config: replay file <path> not readable !startupnever printed — the condition guarding it cannot be true
busmaster: failed to init the file counter, check log folder permissionsstartupbusmaster: failed to init file counter, then startup fails
busmaster: next log file index is <n>startupnormal
busmaster: failed to create file: <path>startup or rotationexit 0
csv: failed to init the file counter, check log folder permissionsfirst frame of a seriescsv: failed to init file counter
csv: next log file index is <n>first frame of a seriesnormal
csv: failed to create file: <path>first frame, or rotationexit 0

Exit status ​

Every exit written in this application is 0, including all of the failures above. There is no non-zero exit anywhere in either recorder.

The practical consequence, covered in Troubleshooting, is that under Restart=always a broken recorder loops silently and is never marked failed.

Termination on SIGINT or SIGTERM leaves the main loop, closes the capture, writes the closing lines where the format has them, and starts the final compression. That path lives in the shared MUXEN library that supplies main(), not in this application.

Files ​

PathContent
/usr/bin/muxen-can-dataloggerthe raw recorder and replay tool
/usr/bin/muxen-csv-dataloggerthe decoding recorder
/usr/lib/systemd/system/muxen-can-datalogger@.serviceraw recorder template unit
/usr/lib/systemd/system/muxen-csv-datalogger@.servicedecoding recorder template unit
/usr/share/bash-completion/completions/muxen-can-dataloggercompletion file — see the note below
/usr/share/bash-completion/completions/muxen-csv-dataloggercompletion file — see the note below
/var/lib/muxen/datalogger/the log folder, created by the package
/usr/bin/gzipexecuted after every rotation. Not shipped here; a package dependency

Both completion files are copies of the muxen-boat completion: they test for muxen-boat and register a completion for muxen-boat. Neither completes either datalogger command.

The recorders create no socket, no runtime directory, no PID file and no state outside the log folder. They read no configuration file.

Output file names ​

RecorderPattern
raw<logFolder>/<interface>.<index>.log
decoded<logFolder>/<interface>_device_<function>_<instance>_<broadcastId>.<index>.log

<index> is a 16-bit counter, chosen at startup as one more than the highest index already present for that pattern — compressed files included. A closed file gains a .gz suffix. Full details in Log files.

systemd ​

Both units are templates. The instance — the text between @ and .service — is the name of the CAN interface, and nothing else.

Common to both units:

DirectiveValue
PartOfmulti-user.target
Afternetwork.target
WantedBymulti-user.target
ExecStart/usr/bin/muxen-can-datalogger --interface %i
Restartalways, RestartSec=30
ConditionPathIsDirectory/sys/class/net/%i

ExecStart is identical in both units: muxen-csv-datalogger@.service starts /usr/bin/muxen-can-datalogger as well. That is not a typo in this table.

Neither unit sets User=, Group=, Environment= or EnvironmentFile=, and neither is attached to muxen.target or muxen-deploy.target. Neither passes any option other than --interface, so every other setting takes its built-in default; a drop-in that clears and replaces ExecStart is the way to change one.

Both units apply the same identity, state directory and hardening:

DirectiveValue
User / Groupmuxen — neither recorder runs as root
StateDirectorymuxen/datalogger — systemd creates /var/lib/muxen/datalogger, owns it as muxen:muxen, and makes it writable despite ProtectSystem=
AmbientCapabilitiesCAP_NET_RAW — a SocketCAN raw socket needs it, and a non-root user does not have it
PrivateTmptrue
PrivateDevicesyes
ProtectSystemstrict
ProtectKernelModulesyes
ProtectKernelTunablesyes
ProtectControlGroupsyes
NoNewPrivilegestrue
CapabilityBoundingSetdrops CAP_SYS_ADMIN, CAP_SYS_TIME, CAP_NET_ADMIN
SystemCallFilterdenies @clock, @debug, @module, @mount, @raw-io, @reboot, @swap, @privileged, @resources
SystemCallErrorNumberEPERM

The muxen user and group come from muxen-systemd, which this package depends on for that reason.

--logFolder defaults to /var/lib/muxen/datalogger, which is exactly what StateDirectory= provides. Point it somewhere else and ProtectSystem=strict will make that path read-only unless you add a ReadWritePaths= drop-in for it.

Enabling ​

sh
sudo systemctl enable --now muxen-can-datalogger@can0.service
sudo systemctl enable --now muxen-can-datalogger@can1.service
sudo systemctl enable --now muxen-csv-datalogger@can0.service
sudo systemctl disable --now muxen-can-datalogger@can0.service

Overriding an option, per instance:

sh
sudo mkdir -p /etc/systemd/system/muxen-can-datalogger@can0.service.d
sudo tee /etc/systemd/system/muxen-can-datalogger@can0.service.d/override.conf >/dev/null <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/bin/muxen-can-datalogger --interface %i --maxRecordTime 1800
EOF
sudo systemctl daemon-reload
sudo systemctl restart muxen-can-datalogger@can0.service

The empty ExecStart= is required; without it systemd appends a second command rather than replacing the first.

Rotation and compression ​

PropertyRaw recorderDecoded recorder
Age triggerframe time > file open time + --maxRecordTimesame
Size triggerfile size + 79 > --maxFileSizeBeforeCompressionfile size > --maxFileSizeBeforeCompression
Evaluatedon each arriving frame, before writing itsame, per file
On rotationclosing lines, close, gzip, open next indexclose, gzip, open next index, write header row
Compressionfork() + execve("/usr/bin/gzip", {"--best", <path>})same
Compression optionalnono
Files deletednevernever

The compression child is not checked for success and does not exit if the exec fails.

Packaging ​

FieldValue
Source and binary packagemuxen-datalogger-tools
Sectionmisc
Architectureany, Multi-Arch: foreign
Depends${misc:Depends}, ${shlibs:Depends}, gzip (>= 1.9)
Build-Dependsdebhelper-compat (= 13), meson, cmake, libmosquitto-dev, libglib2.0-dev
Standards-Version4.7.2
Homepagehttps://www.muxen.fr
Source format3.0 (native)
Directories created/var/lib/muxen/datalogger

The package depends on no other muxen-* package. It declares no trigger and ships no maintainer script, so installing it starts nothing and upgrading it does not restart anything by itself.

CI builds it for bookworm and trixie, on amd64 and arm64.

Building from source ​

sh
meson setup build
meson compile -C build

The makefile wraps the common cases: make deb builds the Debian package, make dev does a clean rebuild and runs meson test, make lint and make lint-check run clang-format.

Dependencies: GLib, and the MUXEN libcanmqtt and libstdmuxen, which are resolved as installed packages if present and as Meson subprojects otherwise. The build enables link-time optimisation, full RELRO and immediate binding.

There is no test suite, and meson test has nothing to run.

Integration of multiplexed solutions
MUXEN and the MUXEN logo are trademarks of MUXEN SAS.