Skip to content

Implement DMR log streaming via /logs endpoint#807

Merged
ilopezluna merged 2 commits into
mainfrom
deciduous-den
Mar 30, 2026
Merged

Implement DMR log streaming via /logs endpoint#807
ilopezluna merged 2 commits into
mainfrom
deciduous-den

Conversation

@ilopezluna
Copy link
Copy Markdown
Contributor

@ilopezluna ilopezluna commented Mar 30, 2026

Implement DMR log streaming via /logs endpoint

Adds a GET /logs HTTP endpoint and refactors the docker model logs CLI command to share log merging and tailing logic through a new pkg/logs package.

What's new:

  • GET /logs endpoint — streams service and engine logs merged in chronological order. Enabled when MODEL_RUNNER_LOG_DIR is set.
  • Query parameters: ?follow=true (real-time tail), ?no-engines=true (exclude engine logs)
  • docker model logs CLI command now supports both local file access and remote API fallback
  • Shared pkg/logs package with MergeLogs and Follow functions, covered by unit tests

Usage

export MODEL_RUNNER_LOG_DIR="$HOME/Library/Containers/com.docker.docker/Data/log/host"
export MODEL_RUNNER_PORT=8080 
make run

# Fetch merged logs
curl http://localhost:8080/logs

# Exclude engine logs
curl http://localhost:8080/logs?no-engines=true

# Stream logs in real-time (follow mode)
curl -N http://localhost:8080/logs?follow=true

# Combine both
curl -N "http://localhost:8080/logs?follow=true&no-engines=true"

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors log handling by centralizing merging and following logic into a new pkg/logs package and introduces a /logs API endpoint for remote log access. Feedback was provided regarding the timestamp parsing format string, which should use placeholders for variable-length fractional seconds to ensure correct chronological merging.

Comment thread pkg/logs/merge.go Outdated
@ilopezluna
Copy link
Copy Markdown
Contributor Author

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the log fetching and merging logic into a dedicated pkg/logs package and introduces a new /logs HTTP endpoint to support log streaming in Docker Desktop environments where local files might be inaccessible. The CLI now supports falling back to this API. Feedback was provided regarding the pollMode setting in the HTTP handler; since the server runs in a Linux VM even on Windows hosts, the current OS check is insufficient to enable polling for host-mounted volumes, which is necessary for reliable log tailing.

Comment thread pkg/logs/http_handler.go
// Follow: tail from recorded offsets using a flushing writer.
// pollMode is needed on Windows where filesystem event
// notifications are unreliable.
pollMode := runtime.GOOS == "windows"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The pollMode is only enabled when runtime.GOOS is "windows". However, the model runner server executes within the Docker Desktop VM, where runtime.GOOS will be "linux", regardless of the host OS. When Docker Desktop runs on a Windows host, log files are on a host-mounted volume where filesystem notifications are unreliable, making polling necessary for tail to function correctly. Without polling, the log streaming feature (--follow) will likely not work for users on Windows.

To ensure reliability across all host platforms, polling should be enabled unconditionally for the HTTP handler.

Suggested change
pollMode := runtime.GOOS == "windows"
pollMode := true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DMR runs on the host no in the DD VM

@ilopezluna ilopezluna marked this pull request as ready for review March 30, 2026 07:53
@ilopezluna ilopezluna changed the title [WIP] implement DMR log streaming via /logs endpoint Implement DMR log streaming via /logs endpoint Mar 30, 2026
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The timestamp layout in pkg/logs/merge.go looks incorrect: Go time layouts must use the reference digits "000000000" for the fractional seconds, but timeFmt is defined as "2006-01-02T15:04:05.999999999Z", which will cause time.Parse to fail on otherwise valid log timestamps; this should likely be reverted to use zeros.
  • In pkg/logs/http_handler.go, errors from MergeLogs are written into the response body after a 200 status and partial content may already be sent; if you need clients to distinguish failure, consider buffering the merge output or performing a lightweight pre-check so that you can send an appropriate non-200 status before any body bytes are written.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The timestamp layout in pkg/logs/merge.go looks incorrect: Go time layouts must use the reference digits "000000000" for the fractional seconds, but timeFmt is defined as "2006-01-02T15:04:05.999999999Z", which will cause time.Parse to fail on otherwise valid log timestamps; this should likely be reverted to use zeros.
- In pkg/logs/http_handler.go, errors from MergeLogs are written into the response body after a 200 status and partial content may already be sent; if you need clients to distinguish failure, consider buffering the merge output or performing a lightweight pre-check so that you can send an appropriate non-200 status before any body bytes are written.

## Individual Comments

### Comment 1
<location path="cmd/cli/commands/logs.go" line_range="84" />
<code_context>
+			if localErr != nil {
+				// Local files unavailable (e.g. running inside a container).
+				// Fall back to the DMR /logs API.
+				apiErr := desktopClient.Logs(
+					cmd.Context(), follow, noEngines, cmd.OutOrStdout(),
+				)
</code_context>
<issue_to_address>
**issue (bug_risk):** fmt.Errorf is used with two %w verbs, which will panic at runtime; only a single %w is allowed.

You’ll need to wrap only one of these errors and format the other normally. For example:

```go
return fmt.Errorf(
    "local logs unavailable (%v); API fallback failed: %w",
    localErr, apiErr,
)
```

or:

```go
return fmt.Errorf(
    "local logs unavailable: %w; API fallback failed: %v",
    localErr, apiErr,
)
```
</issue_to_address>

### Comment 2
<location path="pkg/logs/follow.go" line_range="56-58" />
<code_context>
+
+	// Engine log is optional; a missing file is tolerated.
+	var et *tail.Tail
+	if engineLogPath != "" {
+		et, err = tail.TailFile(engineLogPath, makeCfg(offsets.EngineOffset))
+		if err != nil {
+			et = nil
+		} else {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Errors opening the engine log for tailing are silently ignored, which can hide real problems and drop engine logs.

If `tail.TailFile` fails (e.g., permissions, I/O issues), the code just sets `et = nil` and continues, so the caller gets a nil error while engine logs are never tailed. Since `MergeLogs` may have already read from the engine log, this silent drop is misleading. Please either return/log the error here, or treat only "file not found" as non-fatal, e.g.:

```go
if err != nil {
    if os.IsNotExist(err) {
        et = nil
    } else {
        return fmt.Errorf("tail engine log: %w", err)
    }
}
```

Suggested implementation:

```golang
import (
	"os"

```

```golang
	// Engine log is optional; a missing file is tolerated.
	var et *tail.Tail
	if engineLogPath != "" {
		et, err = tail.TailFile(engineLogPath, makeCfg(offsets.EngineOffset))
		if err != nil {
			if os.IsNotExist(err) {
				// Missing engine log is non-fatal; just skip tailing it.
				et = nil
			} else {
				return fmt.Errorf("tail engine log: %w", err)
			}
		} else {
			defer et.Cleanup()
			defer func(et *tail.Tail) {
				_ = et.Stop()
			}(et)
		}
	}

```

1. Ensure `"os"` is not already imported; if it is, merge the import lines instead of duplicating.
2. Keep the import block formatted according to `gofmt` (the Go tooling will usually reorder imports automatically).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread cmd/cli/commands/logs.go
Comment thread pkg/logs/follow.go
@ilopezluna ilopezluna requested a review from a team March 30, 2026 08:31
Copy link
Copy Markdown
Contributor

@doringeman doringeman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the llama.cpp logs if I run it on the host on macOS on MODEL_RUNNER_HOST=http://localhost:8080 docker model run smollm2 hi.

@doringeman
Copy link
Copy Markdown
Contributor

This ^ is not a bug introduced by this PR though.

Copy link
Copy Markdown
Contributor

@ericcurtin ericcurtin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, pending passing tests

@ilopezluna
Copy link
Copy Markdown
Contributor Author

I don't see the llama.cpp logs if I run it on the host on macOS on MODEL_RUNNER_HOST=http://localhost:8080 docker model run smollm2 hi.

@doringeman yeah this PR does not solve this issue. The goal of this PR is to solve the issue where DMR runs on a host via Docker Desktop and you run docker model logs from inside a container

@ilopezluna ilopezluna merged commit 01e98c3 into main Mar 30, 2026
19 of 22 checks passed
@ilopezluna ilopezluna deleted the deciduous-den branch March 30, 2026 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants