# lurk: a pretty, deliberately smaller alternative to strace

> lurk is a Rust system call tracer that aims at the same job as strace, tracing a command or attaching to a running process, but with an opinionated subset of flags, colorized output and a JSON mode for piping into jq. The trade is a smaller surface in exchange for less power, and the syscall tables ship for only three architectures.

**JakWai01/lurk** — A pretty (simple) alternative to strace.

- Repository: https://github.com/JakWai01/lurk
- Stars: 1,159 · Forks: 44
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/jakwai01-lurk

## A tracer that chooses to be smaller

The README states the project in one line, lurk is a simple and pretty alternative to strace, and then explains the difference in approach. It allows the user to trace system calls of a process or of a command, and in contrast to strace, lurk focuses on a simple, yet pretty, user experience while implementing the most important use cases of strace.

That is the whole design in one sentence, and it is a deliberate trade rather than an unfinished project. The feature list has three entries. It supports an opinionated subset of strace flags, it supports JSON output for further processing via something like jq, and it uses colorized, formatted output.

The audience is therefore a person who runs a command, sees something unexpected, and wants to know which system calls happened. A trace they can read at a glance in a terminal serves that case better than a complete but uniform log, and the JSON mode covers the opposite case where the trace is data to be filtered rather than a screen to be read.

The implementation is Rust, and the dependency list explains the shape of it. clap with the derive feature handles the command line, nix 0.30.1 with the personality, ptrace and signal features provides the process control, syscalls 0.8 with the full feature supplies the syscall tables, uzers 0.12 handles the user and group switching that the username option needs, comfy-table 7 and console 0.16.1 do the formatting, and serde with serde_json provide the JSON output. regex 1 is there for the regular expression support in the trace filter, and anyhow 1 and byteorder 1 are the small remaining pieces.

The project is by Jakob Waibel and Yuri Astrakhan, the edition is 2021, and the crate version is 0.3.14. The repository carries both LICENSE-APACHE and LICENSE-MIT files and the manifest declares MIT OR Apache-2.0, while the licence field in the repository's own metadata says Apache-2.0, so the two files are the ones to read when the choice matters.

## Three ways to install it

The installation section gives three routes, and they cover the common cases without a package manager of your own.

The first is a release tarball fetched with curl and installed with install:

```sh
curl -L -o /tmp/lurk.tar.gz "https://github.com/JakWai01/lurk/releases/latest/download/lurk-x86_64-unknown-linux-gnu.tar.gz"
tar -xzf /tmp/lurk.tar.gz -C /tmp
sudo install /tmp/lurk /usr/local/bin
```

Note the architecture in that URL. The download is for x86_64 on Linux, which is the same constraint the features in the manifest impose, and it is the reason the release asset naming matters if you are on something else.

The second is Cargo, and the package name is not the binary name:

```sh
cargo install lurk-cli
```

The manifest explains the difference. The package is lurk-cli, and the binary it installs is lurk, declared with a bin entry whose path is src/main.rs. So cargo install lurk-cli gives you a command called lurk.

The third is the distribution package for Arch Linux:

```sh
pacman -S lurk
```

The README's demo is a screencast, which is the right medium for a tool whose main selling point is how its output looks, and the repository has an assets/ directory with it.

## The flag set is deliberately narrower than strace's

The help output in the README is the best single description of the tool, and it is short enough to read in full.

The help output lists seventeen options. The filtering and shaping ones are -c and --summary-only to report a summary instead of the regular output, -C and --summary for a summary in addition to the regular output, -j and --json for JSON output, -o and --file to name the file to print output to, -s and --string-limit for the maximum string size to print, -v and --no-abbrev to print unabbreviated versions of strings, and -n and --syscall-number to display system call numbers.

The execution and attachment ones are -p and --attach to attach to a running process, -f and --follow-forks to trace child processes as they are created by currently traced processes, -u and --username to run the command with the uid, gid and supplementary groups of a named user, and -E and --env where --env var=val adds an environment variable and --env var removes one.

Then two that only make sense in the context of a tracer, -z and --successful-only to print only syscalls that returned without an error code, and -Z and --failed-only to print only syscalls that returned with an error code. And -T and --syscall-times, which shows the time spent in system calls in milliseconds.

The two output-shaping flags that deserve a note are -s and -v. A tracer that prints strings has to choose how much of a buffer to dump, and a maximum string size plus a switch for unabbreviated versions are the two knobs that address it.

One detail in the help text is worth flagging. The banner reads lurk 0.2.7, while the package version in the manifest is 0.3.14, so the help output in the README predates the current release and you should not treat the listed version as what you will get.

## Filtering with --expr, and the one qualifier lurk supports

The filtering section is where lurk is most obviously a subset, and the README says so plainly. Unlike in strace, lurk only supports --expr trace. Since this flag behaves almost exactly like in strace, the README quotes a short, slightly changed, excerpt of the strace manpage to explain it.

The grammar is the strace one. The value is a syscall_set defined as [!]value[,value], where a value can be a syscall specified by name, a ?value form that allows suppression of the error in case no syscalls matched the qualification, or a /regex that traces only the system calls matching the regex using POSIX Extended Regular Expression syntax.

Then there are the grouping qualifiers, %file for system calls taking a file name as an argument, %process for the process lifecycle calls of creation, exec and termination, %network with the %net alias, %signal, %ipc, %desc for file descriptor calls, and %memory for the memory mapping calls. There are also %creds for the calls that read or modify user and group identifiers or capability sets, %stat, %lstat and %fstat for the stat variants, %%stat for all of them together, and %statfs for the statfs family.

The quoted text also explains the deprecations, with the note that the syntax without a preceding percent sign, such as -e trace=file, is deprecated, and the same applies to trace=process, trace=network, trace=signal, trace=ipc, trace=desc and trace=memory.

So the filter language is familiar, the syscall_set vocabulary is the strace one, and the limitation is a single one: trace is the only qualifier. If your strace habit is -e trace=openat or -e trace=%file, it transfers directly. If it is -e status=successful or -e read=5, it does not, although the successful-only and failed-only flags cover the common case of that last one.

## Attaching to a running process needs root

There are two ways of tracing system calls, and the README shows both with real output.

You can execute a command directly, and the example is lurk ls:

```sh
$ lurk ls
[54605] execve("", "", "") = 0
[54605] brk(NULL) = 0x55578000
[54605] arch_prctl(12289, 0xffffe780) = -22
[54605] access("", 4) = -2
[54605] openat(4294967196, "/etc/ld.so.cache", 524288) = 3
```

Or you can attach to a running process by providing the process ID via --attach, and in that case the command has to be run with escalated privileges:

```sh
$ sudo lurk --attach $PID
[54683] epoll_wait(5, 0xd01a3c20, 8, 4294967295) = -4
[54683] recvmsg(3, 0x4a4a0020, 0) = -11
[54683] clock_gettime(1, 0x4a49df40) = 0
```

The difference between the two examples is instructive. The first shows the startup of a program, with the dynamic loader work, opening the shared library cache, the memory mappings. The second shows a running server in its event loop, where the interesting activity is epoll_wait and recvmsg and a great deal of clock_gettime.

That is the practical use for a tracer like this. The startup case is what a strace user reaches for first, and the running process case is what you want when a service is misbehaving in production and you cannot restart it.

The privilege requirement is the one operational fact to plan around. Attaching uses ptrace, and the nix dependency enables the ptrace feature, so the tool is asking the kernel for the same facility strace uses. On a shared host, the process needs to be one you are allowed to inspect.

## Syscall tables for three architectures, and the rest are commented out

The features section of the manifest is where the portability limit lives, and it is easy to read past because the names are mostly commented out.

The default feature set is empty. The all feature enables the syscall tables for all architectures, and its list currently contains three entries, x86_64, riscv64 and aarch64. The individual feature flags aarch64, x86_64 and riscv64 exist and are empty, which is the normal way to make an architecture selectable.

Everything else is commented out, with the list of names still visible in the source: arm, mips, mips64, powerpc, powerpc64, s390x, sparc, sparc64 and x86. The comment above the block explains what the feature does, that it includes the syscall tables for all architectures.

So the effective statement is that lurk currently traces on x86_64, aarch64 and riscv64, and that the other architectures are one uncomment away in principle but not currently built in. The same limitation explains the release tarball URL, which is explicitly the x86_64 unknown linux gnu build.

The rest of the repository supports the development story. There is a justfile that wraps the commands the project uses, including a ci-test recipe that runs rust-info, test-fmt, clippy, check, test and test-doc in sequence, a check recipe that runs cargo check with RUSTFLAGS set to deny warnings, a clippy recipe that runs across the workspace and all targets with warnings denied, and a test recipe that does the same for cargo test. Formatting uses cargo plus nightly fmt with the imports_granularity and group_imports settings, and there is an examples/tracer.rs showing how the library side is used.

## Where lurk is the wrong tool

The limits are clear enough to state without hedging.

The first is the flag surface. The README says it plainly, an opinionated subset of strace flags. A single qualifier is supported for --expr. Anything strace can do through other qualifiers, through decoding of specific structures, or through its extensive output configuration, lurk does not do, and the project is not trying to. If your workflow depends on those, the answer is strace and not a switch in lurk.

The second is the architecture set. Three architectures are enabled and the rest are commented out in the manifest, so on anything else the tool cannot name the system calls it is tracing. That is a hard boundary rather than a rough edge.

The third is scope. lurk traces system calls. It is not a debugger, it does not set breakpoints, and it does not let you look at memory. The colour, the formatting and the summary flags make the output pleasant to read, but the information is the same information a plain tracer would give you.

The fourth is the trace itself. A system call trace of a busy process is a lot of output, and lurk's answer to that is the summary flags, the string limit, the successful-only and failed-only filters and the JSON mode for piping into jq. Those work well, but they are filters over a stream, not a sampler, and a process in a tight loop will still produce a lot of lines.

The fifth is the version string mismatch between the README's help output and the current package version, which is a small thing but a reminder that the documentation in the repository lags the code. The flags you rely on should be checked against the help output of the binary you actually installed.

## Conclusion

Adopt lurk if you want a system call trace that is readable in a terminal, that you can pipe through jq, and that does not need the full strace flag surface, particularly if you work on x86_64, aarch64 or riscv64. Do not adopt it if you depend on strace features lurk does not implement, such as the other --expr qualifiers, and do not adopt it on the architectures whose syscall tables are commented out in the manifest. Verify first that the trace output is complete enough for your debugging question, that the string and abbreviation options behave as you expect for the calls you care about, and that the version you install matches the documentation, since the help text in the README still shows 0.2.7 while the package is at 0.3.14.

## FAQ

### How do I install lurk?

There are three documented routes: a release tarball with curl and install into /usr/local/bin, cargo install lurk-cli, which installs a binary named lurk, and pacman -S lurk on Arch Linux. The release tarball URL is for the x86_64 unknown linux gnu build.

### How do I filter which system calls lurk traces?

With --expr, and lurk only supports the trace qualifier. The syntax is the strace one, a comma separated syscall set where a value can be a syscall name, a ?value form that suppresses the no match error, or a /regex using POSIX extended regular expressions, plus the %file, %process, %network, %signal, %ipc, %desc, %memory and %creds groupings.

### Does lurk need root to attach to a running process?

Yes. The README says that when attaching to a running process by providing the process ID via --attach, the command has to be run with escalated privileges, and the example is sudo lurk --attach $PID.

### Which CPU architectures does lurk support?

The all feature in the manifest enables the syscall tables for x86_64, riscv64 and aarch64, and the features for arm, mips, powerpc, s390x, sparc and x86 are commented out. The default feature set is empty, so the architecture has to be selected at build time.

## Sources

- [Issues](https://github.com/JakWai01/lurk/issues)
- [JakWai01/lurk on GitHub](https://github.com/JakWai01/lurk)
- [License: Apache-2.0](https://github.com/JakWai01/lurk/blob/main/LICENSE)
- [README](https://github.com/JakWai01/lurk/blob/main/README.md)
- [Releases](https://github.com/JakWai01/lurk/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jakwai01-lurk
