Skip to content

fix: periodically flush file logs - #6070

Merged
joanlopez merged 4 commits into
grafana:masterfrom
rohan-patnaik:rpatnaik/fix-file-log-periodic-flush
Jul 30, 2026
Merged

joanlopez merged 4 commits into
grafana:masterfrom
rohan-patnaik:rpatnaik/fix-file-log-periodic-flush

Conversation

@rohan-patnaik

Copy link
Copy Markdown
Contributor

What?

Flush file log output once per second while k6 is running.

Why?

File logs are buffered today. If k6 is killed before shutdown, recent console logs can stay in memory and never reach the file.

Closes #4657

Testing

  • go test ./internal/log

@rohan-patnaik
rohan-patnaik marked this pull request as ready for review June 16, 2026 04:46
@rohan-patnaik
rohan-patnaik requested a review from a team as a code owner June 16, 2026 04:46
@rohan-patnaik
rohan-patnaik requested review from inancgumus and oleiade and removed request for a team June 16, 2026 04:46
@inancgumus
inancgumus requested a review from Copilot June 16, 2026 17:48

Copilot AI left a comment

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.

Pull request overview

This PR addresses buffered file log output in k6’s async file logging hook by periodically flushing the bufio.Writer during execution, reducing log loss when the process is terminated before graceful shutdown.

Changes:

  • Add a 1s ticker in fileHook.Listen() to flush the buffered logfile writer periodically while k6 is running.
  • Add a unit test that validates periodic flushing occurs even without shutdown.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
internal/log/file.go Introduces a periodic flush ticker in the file logging hook’s Listen() loop.
internal/log/file_test.go Adds a test to assert that the file hook flushes periodically during runtime.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/log/file.go
Comment on lines +124 to +127
case <-ticker.C:
if err := h.bw.Flush(); err != nil {
h.fallbackLogger.Errorf("failed to flush logfile buffer: %w", err)
}
Comment thread internal/log/file_test.go
Comment on lines +170 to +175
hook := &fileHook{
loglines: make(chan []byte),
w: nc,
bw: bufio.NewWriter(nc),
levels: logrus.AllLevels,
}
@rohan-patnaik
rohan-patnaik temporarily deployed to azure-trusted-signing June 16, 2026 17:54 — with GitHub Actions Inactive
@rohan-patnaik
rohan-patnaik temporarily deployed to azure-trusted-signing June 16, 2026 17:56 — with GitHub Actions Inactive

@inancgumus inancgumus left a comment •

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.

@rohan-patnaik, please check the Copilot review. Thanks.

@rohan-patnaik

Copy link
Copy Markdown
Contributor Author

Addressed Copilot's comments: file hook errors now use structured logging and the flush test uses a buffered channel.

@rohan-patnaik
rohan-patnaik temporarily deployed to azure-trusted-signing June 17, 2026 23:13 — with GitHub Actions Inactive
@rohan-patnaik
rohan-patnaik temporarily deployed to azure-trusted-signing June 17, 2026 23:15 — with GitHub Actions Inactive
@oleiade
oleiade temporarily deployed to azure-trusted-signing July 9, 2026 16:26 — with GitHub Actions Inactive
@oleiade
oleiade temporarily deployed to azure-trusted-signing July 9, 2026 16:29 — with GitHub Actions Inactive
@oleiade

oleiade commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Hi @rohan-patnaik

The grafana organization now enforces every single commits to be signed/verified by GitHub. Would you mind setting up signing on Git+GitHub and rewriting your commits?

Let us know if you need any support with setting that up.

@rohan-patnaik

Copy link
Copy Markdown
Contributor Author

@oleiade Thanks for the heads up. I’m planning to set up commit signing locally, rewrite the PR commits so GitHub marks them as verified, and then force-push the updated branch with --force-with-lease.

I’ll keep the actual code changes the same. Does that all sound okay?

@ankur22 ankur22 added the awaiting user waiting for user to respond label Jul 16, 2026
@rohan-patnaik
rohan-patnaik force-pushed the rpatnaik/fix-file-log-periodic-flush branch from e29b739 to 452d17f Compare July 21, 2026 13:49
@rohan-patnaik
rohan-patnaik temporarily deployed to azure-trusted-signing July 23, 2026 13:32 — with GitHub Actions Inactive
@rohan-patnaik
rohan-patnaik temporarily deployed to azure-trusted-signing July 23, 2026 13:34 — with GitHub Actions Inactive
@joanlopez
joanlopez temporarily deployed to azure-trusted-signing July 30, 2026 13:28 — with GitHub Actions Inactive
@joanlopez
joanlopez temporarily deployed to azure-trusted-signing July 30, 2026 13:30 — with GitHub Actions Inactive
@joanlopez joanlopez added this to the v2.2.0 milestone Jul 30, 2026
@joanlopez
joanlopez merged commit 7dbe86e into grafana:master Jul 30, 2026
57 of 58 checks passed
@ankur22 ankur22 removed the awaiting user waiting for user to respond label Aug 3, 2026
social4hyq pushed a commit to social4hyq/homebrew-core that referenced this pull request Sep 20, 2026
k6 2.2.0

Created-by: HarmonybrewBot
Commit-by: HarmonybrewBot
Merged-by: HarmonybrewBot
Description: Created by `brew bump`

---

Created with `brew bump-formula-pr`.<details>
  <summary>release notes</summary>
  <pre>k6 `v2.2.0` is here 🎉! This release includes:

- `k6 cloud run --local-execution` now streams k6's logs to Grafana Cloud, so the test run's log view works for local execution too.
- `chromium.connectOverCDP()`, which connects browser tests to an already-running Chromium instance.
- `TextEncoder` and `TextDecoder` available as globals, and `WritableStream` support in `k6/experimental/streams`.
- A `k6 cloud load-zone list` command.
- Two new experimental feature flags: `merge-run-tags` and `freeze-env`.

## Breaking changes

There are no breaking changes in this release.

## New features

### `k6 cloud run --local-execution` streams logs to Grafana Cloud [#6171](grafana/k6#6171)

When a cloud test runs locally with `k6 cloud run --local-execution`, k6's logs now stream to the Grafana Cloud test run, so the run's log view is populated the same way it is for cloud execution. Previously, local-execution logs stayed on the machine running k6 and never reached the cloud. Work with Grafana's secrets management to safely work with secrets and redact them if they're accidentally leaked into logs are pushed. Use the new `--no-cloud-logs` flag opts out to opt out of streaming of logs when working with `--local-execution`:

```shell
k6 cloud run --local-execution script.js
k6 cloud run --local-execution --no-cloud-logs script.js
```

### Connect to a running browser with `chromium.connectOverCDP()` [#6165](grafana/k6#6165)

The browser module can now attach to an existing Chromium-based browser over the Chrome DevTools Protocol, mirroring Playwright's `browserType.connectOverCDP()`. Pass the browser's WebSocket endpoint and k6 manages the returned browser's connection — it's auto-closed at the end of the iteration, though you can call `close()` earlier to release the connection on demand.

```js
import { chromium } from 'k6/browser';

export default async function () {
  const browser = await chromium.connectOverCDP('ws://localhost:9222/devtools/browser/<id>');
  const page = await browser.newPage();

  try {
    await page.goto('https://quickpizza.grafana.com/');
  } finally {
    await page.close();
    await browser.close();
  }
}
```

Unlike the `K6_BROWSER_WS_URL` environment variable, the endpoint is a runtime value — you can, for example, request a fresh session URL from a browser provider's API in `setup()` and connect to it from the iterations.

### `TextEncoder` and `TextDecoder` globals [#6182](grafana/k6#6182)

`TextEncoder` and `TextDecoder` are now available as standard globals in both the init and VU contexts, no import required — matching how they are exposed in browsers and other JavaScript runtimes.

```js
const encoded = new TextEncoder().encode('Hello, world!');
const decoded = new TextDecoder().decode(encoded);
```

### `WritableStream` in `k6/experimental/streams` [#6132](grafana/k6#6132)

The experimental streams module now implements `WritableStream` and `WritableStreamDefaultWriter` following the WHATWG Streams specification, complementing the existing `ReadableStream` and paving the way for a future `TransformStream` implementation.

```js
import { WritableStream } from 'k6/experimental/streams';

export default async function () {
  const stream = new WritableStream({
    write(chunk) {
      console.log(`wrote ${chunk}`);
    },
  });

  const writer = stream.getWriter();
  await writer.write('hello');
  await writer.close();
}
```

### `k6 cloud load-zone list` command [#6142](grafana/k6#6142)

A new `k6 cloud load-zone list` subcommand lists the load zones — public and private — available in the configured Grafana Cloud k6 stack, mirroring the existing `k6 cloud project list` command. Output defaults to a human-readable table; pass `--json` to emit a JSON array instead.

```console
$ k6 cloud load-zone list
Load zones for https://example.grafana.net:

ID                     NAME                     TYPE      AVAILABLE
amazon:us:ashburn      Ashburn, US (Amazon)     public    yes
amazon:sa:cape town    Cape Town, SA (Amazon)   public    yes
```

### Configurable `handleSummary()` timeout [#5854](grafana/k6#5854)

The time budget for the `handleSummary()` callback — previously hardcoded to 120 seconds — is now configurable through the `handleSummaryTimeout` option or the `K6_HANDLE_SUMMARY_TIMEOUT` environment variable, so long-running tests with heavy summaries no longer fail with `handleSummary() execution timed out`. Thanks, @LBaronceli!

```js
export const options = {
  handleSummaryTimeout: '5m',
};
```

### New experimental feature flags: `merge-run-tags` and `freeze-env`

Two new experimental flags join the feature-flag system introduced in v2.1.0:

- [#5714](grafana/k6#5714) `merge-run-tags` merges run tags per key across config layers, so `options.tags` in a script is no longer silently discarded when `--tag` or `K6_TAGS` is also used — higher-priority layers win on conflicting keys instead of replacing the whole map. Thanks, @yordis!
- [#6032](grafana/k6#6032) `freeze-env` freezes the `__ENV` object, so modifications from script code throw a `TypeError` (in strict mode) instead of silently persisting across iterations and scenarios. Thanks, @lohitkolluri!

```shell
k6 run --features merge-run-tags,freeze-env script.js
```

## UX improvements and enhancements

- [#5631](grafana/k6#5631) Makes the browser module's header accessors — `response.allHeaders()`, `headerValue()`, `headerValues()`, and `headersArray()` — return the raw wire headers (including `Set-Cookie` and security-related headers), correctly paired with each hop of a redirect chain instead of Chrome's provisional headers. As part of this, `headerValues()` now matches header names case-insensitively and splits repeated values on newlines rather than commas, and the `browser_data_sent`/`browser_data_received` metrics now include the raw header bytes and no longer vary run-to-run with CDP event ordering.
- [#6208](grafana/k6#6208) Makes `k6 cloud` reject the run flags (for example, `--vus`) with an `unknown flag` error and a non-zero exit code. Previously `k6 cloud --vus 10 script.js` accepted the flags, printed the help text, and exited 0 — running tests with `k6 cloud` directly was deprecated in v2.0.0 in favor of `k6 cloud run`.
- [#6096](grafana/k6#6096) Points the cloud secrets error at `K6_CLOUD_SECRETS_TOKEN` and `K6_CLOUD_SECRETS_ENDPOINT` when a test run is reused via `K6_CLOUD_PUSH_REF_ID` under `--local-execution`, instead of suggesting the `--local-execution` flag the user is already using.
- [#6196](grafana/k6#6196) Adds `catch` blocks to the browser examples so a failing iteration reports the original error instead of a subsequent `page.close()` failure. Thanks, @locker95!

## Bug fixes

- [#6234](grafana/k6#6234) Classifies HTTP/2 errors by message so the `error_code` metric tag stays correct when k6 is built with Go 1.27 (whose `x/net/http2` delegates to the standard library), and explicitly enables HTTP/2 negotiation on VU transports.
- [#6232](grafana/k6#6232) Drains queued log entries in the Loki hook at shutdown so `--out loki` and cloud log streaming no longer lose the final batch, and emits a `k6 dropped N log messages` warning when the cloud log buffer overflows instead of dropping logs silently.
- [#6125](grafana/k6#6125) Serializes the first concurrent open of a file in the caching filesystem so parallel `fs.open()` calls on the same file no longer read zero or truncated bytes.
- [#6147](grafana/k6#6147) Fixes a data race and inconsistent request-interception state when browser routes are added or removed concurrently. Thanks, @somak2kai!
- [#6070](grafana/k6#6070) Flushes buffered file log output once per second so recent logs aren't lost when k6 is killed before shutdown. Thanks, @rohan-patnaik!
- [#6205](grafana/k6#6205) Stops sending an invalid `Sec-WebSocket-Protocol` header when tailing Grafana Cloud logs; spec-strict servers rejected the handshake with `websocket: bad handshake`.
- [#6200](grafana/k6#6200) Leaves a counter's `rate` unset when the observed duration is zero, instead of computing `+Inf` and spuriously failing `rate` thresholds. Thanks, @samarth70!
- [#6195](grafana/k6#6195) Initializes a gauge's maximum from the first sample so all-negative gauge series no longer report `max=0`. Thanks, @Solaris-star!
- [#6145](grafana/k6#6145) Prevents the OpenTelemetry output from panicking at startup when basic auth is configured without `K6_OTEL_HEADERS`. Thanks, @lukdz!
- [#6140](grafana/k6#6140) Stops `SharedArray` deep-freezing JS primitives, which needlessly wrapped large strings in `String` objects — cutting memory usage in the reported reproduction from roughly 1 GB to 100 MB.

## Maintenance and internal improvements

- [#6126](grafana/k6#6126), [#6224](grafana/k6#6224), [#6229](grafana/k6#6229) Adds anonymous extension usage to the k6 usage report: a run reports the Go module path, version, and type of registry-cataloged extensions it actually uses (imported `k6/x/` modules, output extensions selected with `--out`, and `k6 x` subcommands). Private and unlisted extensions are never reported, and the existing `--no-usage-report` opt-out covers it.
- [#6183](grafana/k6#6183), [#6218](grafana/k6#6218) Updates Sobek and regexp2, making `W

See merge request: Harmonybrew/homebrew-core!16299

This branch was previously deployed

1 inactive deployment
azure-trusted-signing — 54d16cfa Deployed Jul 30, 2026 by joanlopez via sign-packages #10188
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.

When logging to a file, no logs are written if k6 is killed

6 participants