Skip to content

Use a relative target for the latest log symlink so it survives different absolute mount paths (host/Docker) #978

@russkel

Description

@russkel

Description

launch.logging maintains a latest symlink pointing at the most recent log directory. It is created in _renew_latest_log_dir() with an absolute target:

https://github.com/ros2/launch/blob/rolling/launch/launch/logging/__init__.py#L93-L108

def _renew_latest_log_dir(*, log_dir: str) -> bool:
    base_dir = os.path.dirname(log_dir)
    latest_dir = os.path.join(base_dir, 'latest')

    if os.path.lexists(latest_dir):
        if not os.path.islink(latest_dir):
            return False
        os.unlink(latest_dir)
    os.symlink(log_dir, latest_dir, target_is_directory=True)   # absolute target
    return True

Because latest and the dated log directory are always siblings in base_dir, the link target could just as well be relative (the directory's basename). Proposed change:

    os.symlink(
        os.path.basename(log_dir), latest_dir, target_is_directory=True)

Motivation

The absolute target breaks whenever the log directory is viewed under a different absolute path than the one in effect when the link was written. The most common case is a shared/mounted log directory between a host and a Docker container:

  • Container writes logs to /root/.ros/log, so latest -> /root/.ros/log/<run>.
  • The same volume is mounted on the host at e.g. /home/user/ros-logs, where /root/.ros/log/<run> does not exist, so latest dangles.

The reverse (host writes, container reads) fails the same way. It also affects any setup where the logs are relocated, bind-mounted, or inspected from a different mount namespace.

A relative target resolves correctly regardless of the absolute mount point, since latest and its target stay in the same directory. There is no scenario in which latest and the dated directory are not siblings, so the relative form is always equivalent for in-place use and strictly more portable.

Design / Implementation Considerations

  • One-line change in _renew_latest_log_dir(): use os.path.basename(log_dir) as the symlink target instead of the full log_dir.
  • target_is_directory=True is retained (only relevant on Windows).
  • Reading os.path.realpath(latest_dir) / accessing files through latest/ is unchanged for local use — a relative symlink resolves identically when the CWD-independent parent directory is intact.
  • Tools that call os.readlink('.../latest') and expect an absolute path would now get a basename. This seems unlikely (the documented/contract value is "the latest log directory", reachable via the link itself), but it is the one behavioural difference worth noting.
  • Backward compatibility: existing latest links are recreated on the next run, so no migration is needed.

I'm happy to open a PR with the change plus a test asserting the link target is relative and still resolves to the run directory.

Additional Information

We currently work around this downstream by monkey-patching _renew_latest_log_dir to emit a relative target. It would be cleaner for launch to do this by default, as there appears to be no downside for the standard local-use case.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    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