Skip to content

Opt-in nanosecond log timestamps (--log-ns-timestamps) - #6310

Merged
janHildebrandt98 merged 6 commits into
masterfrom
feature/json-log-nanosecond-precision
Aug 27, 2026
Merged

janHildebrandt98 merged 6 commits into
masterfrom
feature/json-log-nanosecond-precision

Conversation

@janHildebrandt98

@janHildebrandt98 janHildebrandt98 commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

What?

Add an opt-in cli flag / env var that switches log timestamps from second to nanosecond precision, for both --log-format=json and the default text format (when --no-color is set).

Core changes:

  1. Introduced new env var K6_LOG_NS_TIMESTAMPS and cli switch --log-ns-timestamps
  2. FallbackLogger and Logger on GlobalState respect that flag and log timestamps in nanoseconds if enabled
  3. Adjusted ReportCaller() to no longer replace the entire formatter on the global logger thus overwriting all config applied to it. It will now simply mutate the existing. Also added a test ensuring functionality of ReportCaller() --> separate PR
  4. Moved the block setting up the log format out of setupLoggers() into a separate function to comply with linted max func length

Decided against feature flag because feature flags are resolved after setupLoggers() already ran so they are not available when needed.

Checklist

  • I have performed a self-review of my code.
  • I have commented on my code, particularly in hard-to-understand areas.
  • I have added tests for my changes.
  • I have run linter and tests locally (make check) and all pass.

Checklist: Documentation (only for k6 maintainers and if relevant)

Please do not merge this PR until the following items are filled out.

  • I have added the correct milestone and labels to the PR.

Related PR(s)/Issue(s)

Closes #6000

@janHildebrandt98 janHildebrandt98 added this to the v2.3.0 milestone Aug 18, 2026
@janHildebrandt98
janHildebrandt98 requested a review from a team as a code owner August 18, 2026 12:45
@janHildebrandt98
janHildebrandt98 requested review from joanlopez and mstoykov and removed request for a team August 18, 2026 12:45
Comment thread internal/cmd/root.go Resolved
@janHildebrandt98
janHildebrandt98 temporarily deployed to azure-trusted-signing August 18, 2026 12:51 — with GitHub Actions Inactive
@janHildebrandt98
janHildebrandt98 temporarily deployed to azure-trusted-signing August 18, 2026 12:54 — with GitHub Actions Inactive
@janHildebrandt98
janHildebrandt98 force-pushed the feature/json-log-nanosecond-precision branch from 71de669 to c868357 Compare August 19, 2026 10:02
@janHildebrandt98
janHildebrandt98 temporarily deployed to azure-trusted-signing August 19, 2026 10:06 — with GitHub Actions Inactive
@janHildebrandt98
janHildebrandt98 temporarily deployed to azure-trusted-signing August 19, 2026 10:08 — with GitHub Actions Inactive
@janHildebrandt98
janHildebrandt98 temporarily deployed to azure-trusted-signing August 20, 2026 07:16 — with GitHub Actions Inactive
@janHildebrandt98
janHildebrandt98 temporarily deployed to azure-trusted-signing August 20, 2026 07:18 — with GitHub Actions Inactive
@janHildebrandt98 janHildebrandt98 changed the title Use nanosecond-precision in JSON log output timestamps (RFC3339Nano) instead of second precision Opt-in nanosecond log timestamps instead of second precision Aug 20, 2026
@janHildebrandt98 janHildebrandt98 changed the title Opt-in nanosecond log timestamps instead of second precision Opt-in nanosecond log timestamps instead of second precision (--log-ns-timestamps) Aug 20, 2026
@janHildebrandt98 janHildebrandt98 changed the title Opt-in nanosecond log timestamps instead of second precision (--log-ns-timestamps) Opt-in nanosecond log timestamps (--log-ns-timestamps) Aug 20, 2026
@janHildebrandt98
janHildebrandt98 temporarily deployed to azure-trusted-signing August 20, 2026 07:26 — with GitHub Actions Inactive
Comment thread internal/cmd/root.go
default:
c.globalState.Logger.SetFormatter(&logrus.TextFormatter{
ForceColors: loggerForceColors, DisableColors: c.globalState.Flags.NoColor,
TimestampFormat: timestampFormat,

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.

When I tested it - if you set this up , but do not set anything else it still shows second difference from the start of the exceution (instead of timestamps) - and to have actual timestamps here you have to est one more option.

Have you tested it ? It might be a bit strange that you set this flag and in the most usual case nothing changes

@janHildebrandt98 janHildebrandt98 Aug 20, 2026 •

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.

Yes that is true. To get actual timestamps you either have to

  1. switch to JSON format
  2. set --no-color, then logrus will emit logfmt including timestamps (that is what SM is doing)

I thought about it and decided that it should be fairly obvious that you can only change timestamps if you actually receiving timestamps. But it could be worth a hint in the docs?
Don't see a way to fix it really. We would need to magically change other things depending on this flag which I think is also strange.

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.

Okay lets have it in the docs - I think the cli help already is good neough. And not certain manyy people will ever hit this either wya

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.

adressed in grafana/k6-docs#2305

mstoykov
mstoykov previously approved these changes Aug 21, 2026
Comment thread cmd/state/state.go
if _, ok := env["NO_COLOR"]; ok {
result.NoColor = true
}
if _, ok := env["K6_LOG_NS_TIMESTAMPS"]; ok {

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.

Even an empty value here will enable this feature. I guess this is what we want, because it looks consistent with other env vars like NO_COLOR or K6_PROFILING_ENABLED, but just wanted to double-check it with you folks. Is that the case? cc/ @mstoykov @janHildebrandt98


Question aside: Is that something we should perhaps try to "fix" for v3? Like, not enabling stuff if the value is not truthy, even if the env var is defined? Or is that some sort of a common pattern, or am I missing some historical context that makes this more reasonable that it sounds at first glance?

Thanks!

@janHildebrandt98 janHildebrandt98 Aug 24, 2026 •

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.

Not sure on this. it is mixed right now. NO_COLOR and K6_PROFILING_ENABLED don't care about the value but K6_AUTO_EXTENSION_RESOLUTION actually parses and only evaluates to true if the value is truthy. We need to decide what we want here.
Short research from my side showed that there does not seem to be a consensus on how to handle this in other apps - both approaches exist

@janHildebrandt98 janHildebrandt98 Aug 24, 2026 •

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.

honestly I think I would prefer actually parsing the value. It feels wrong to have K6_PROFILING_ENABLED=false enable profiling :D

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.

Yes! I'd be even tempted to ship it as a bugfix. But, for instance, for NO_COLOR there's a comment stating this is the desired behavior. Not sure why, tho.


// ReportCaller adds source file and function names to the log entries.
func (l *Logger) ReportCaller() {
func (l *Logger) ReportCaller() error {

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.

It seems to me thatl.ReportCaller() could be called concurrently, right? See the reasoning below ๐Ÿ‘‡๐Ÿป

ReportCaller() runs on every call to makeLogger(), and makeLogger() is invoked from BrowserType.init(), which is called by Launch(), Connect(), and ConnectOverCDP(), so it could be called concurrently when calling newBrowserRegistry once per VU, or when connecting to remote browser instances.

If so, I think we should add mutual exclusion to prevent data races when assigning the formatter attributes below.

A targeted Go test to reproduce the data race could be:

func TestReportCallerRace(t *testing.T) {
	base := logrus.New()
	base.SetOutput(io.Discard)
	base.SetFormatter(&logrus.TextFormatter{})

	var wg sync.WaitGroup
	for i := 0; i < 8; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			l := New(base, "")
			_ = l.ReportCaller()
		}()
	}
	for i := 0; i < 8; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			l := New(base, "")
			for j := 0; j < 100; j++ {
				l.Infof("cat", "hello %d", j)
			}
		}()
	}
	wg.Wait()
}

Do I get it right that we should handle this? Or we're okay with that because the chances are low?

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.

solved since i removed the changes to ReportCaller

@joanlopez joanlopez 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.

I think there's a raw formatter regression.

If I run the following on this branch:

K6_BROWSER_LOG_CALLER=true go run main.go run --log-format=raw - <<'EOF'
import { browser } from 'k6/browser';

export const options = {
  scenarios: {
    ui: {
      executor: 'shared-iterations',
      options: { browser: { type: 'chromium' } },
    },
  },
};

export default async function () {
  const page = await browser.newPage();
  await page.close();
}
EOF

I get error building browser on IterStart: initializing browser type: error setting up logger: unknown formatter type *cmd.RawFormatter at default (file:///-:12:16(0)).

While the same works fine in master.

Can you double check that, please? @janHildebrandt98 Thanks! ๐Ÿ™‡๐Ÿป

Comment thread cmd/state/state.go Outdated
fallbackFormatter := &logrus.TextFormatter{} // no fancy formatting here
if globalFlags.LogNanosecondTimestamps {
fallbackFormatter.TimestampFormat = time.RFC3339Nano
}

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.

I think at this point we haven't parsed CLI flags, so if the user chooses to use --log-ns-timestamp, this will make no effect.

Are we fine with this only working when the user relies on K6_LOG_NS_TIMESTAMPS (cc/ @mstoykov)? Or should we move this down the execution path up to a point where Cobra already did bind the flags?

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.

we should definitely move this. I missed that getFlags() only reads the env vars ๐Ÿคฆโ€โ™‚๏ธ I will come up with something...

@joanlopez joanlopez Aug 25, 2026 •

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.

Yeah, it's not obvious from the method name.. ๐Ÿ˜…

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.

decided to ignore the fallback formatter. Since it will only emit when the actual log output has issues and we are also not applying any other formatting, I figured its better to stay consistent and not introduce any hacks to apply the formatting later.

@janHildebrandt98

Copy link
Copy Markdown
Contributor Author

Decided to move the changes to ReportCaller() out of this PR as they were getting out of hand.. Issue: #6335 will be tackled in a separate PR.

@janHildebrandt98
janHildebrandt98 force-pushed the feature/json-log-nanosecond-precision branch from cc5cf4f to d16092e Compare August 25, 2026 11:52
@janHildebrandt98
janHildebrandt98 temporarily deployed to azure-trusted-signing August 25, 2026 11:58 — with GitHub Actions Inactive
@janHildebrandt98
janHildebrandt98 temporarily deployed to azure-trusted-signing August 25, 2026 12:01 — with GitHub Actions Inactive
@janHildebrandt98
janHildebrandt98 merged commit b1c90cf into master Aug 27, 2026
61 checks passed
@janHildebrandt98
janHildebrandt98 deleted the feature/json-log-nanosecond-precision branch August 27, 2026 13:43

This branch was previously deployed

1 inactive deployment
azure-trusted-signing โ€” d16092ed Deployed Aug 25, 2026 by janHildebrandt98 via sign-packages #10454
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.

Allow specifying logs time unit

4 participants