No results for

Powered byAlgolia

Output Extensions

k6 provides many metrics and output formats, but it cannot directly support all possibilities. To store or alter metrics captured during an active k6 test, you can create a custom output extension.

Output extension binaries can use the --out flag to send metrics to a custom place. Some potential reasons for a custom extension could include:

  • To support a time-series database not already supported
  • To add derived metrics data for storage
  • To filter metrics to only the data you care about

Like JavaScript extensions, output extensions rely on the extension author to implement specific APIs.

Before you start:

To run this tutorial, you'll need the following applications installed:

  • Go
  • Git

You also need to install xk6:

$ go install

Write a simple extension

  1. Set up a directory to work in.

    $ mkdir xk6-output-logger; cd xk6-output-logger; go mod init xk6-output-logger
  2. The core of an Output extension is a struct that implements the output.Output interface.

    Create a simple example that outputs each set of metrics to the console as received by the AddMetricSamples(samples []metrics.SampleContainer) method of the output interface.

    package log
    import (
    // AddMetricSamples receives metric samples from the k6 Engine as they're emitted.
    func (l *Logger) AddMetricSamples(samples []metrics.SampleContainer) {
    for _, sample := range samples {
    all := sample.GetSamples()
    fmt.Fprintf(l.out, "%s [%s]\n", all[0].GetTime().Format(time.RFC3339Nano), metricKeyValues(all))
    // metricKeyValues returns a string of key-value pairs for all metrics in the sample.
    func metricKeyValues(samples []metrics.Sample) string {
    names := make([]string, 0, len(samples))
    for _, sample := range samples {
    names = append(names, fmt.Sprintf("%s=%v", sample.Metric.Name, sample.Value))
    return strings.Join(names, ", ")
  3. Register the module to use these from k6 test scripts.

    import ""
    // init is called by the Go runtime at application startup.
    func init() {
    output.RegisterExtension("logger", New)

You must use the registered with the -o, or --out flag when running k6!

The final extension code looks like this:

1package log
3import (
4 "fmt"
5 "io"
6 "strings"
7 "time"
9 ""
10 ""
13// init is called by the Go runtime at application startup.
14func init() {
15 output.RegisterExtension("logger", New)
18// Logger writes k6 metric samples to stdout.
19type Logger struct {
20 out io.Writer
23// New returns a new instance of Logger.
24func New(params output.Params) (output.Output, error) {
25 return &Logger{params.StdOut}, nil
28// Description returns a short human-readable description of the output.
29func (*Logger) Description() string {
30 return "logger"
33// Start initializes any state needed for the output, establishes network
34// connections, etc.
35func (o *Logger) Start() error {
36 return nil
39// AddMetricSamples receives metric samples from the k6 Engine as they're emitted.
40func (l *Logger) AddMetricSamples(samples []metrics.SampleContainer) {
41 for _, sample := range samples {
42 all := sample.GetSamples()
43 fmt.Fprintf(l.out, "%s [%s]\n", all[0].GetTime().Format(time.RFC3339Nano), metricKeyValues(all))
44 }
47// metricKeyValues returns a string of key-value pairs for all metrics in the sample.
48func metricKeyValues(samples []metrics.Sample) string {
49 names := make([]string, 0, len(samples))
50 for _, sample := range samples {
51 names = append(names, fmt.Sprintf("%s=%v", sample.Metric.Name, sample.Value))
52 }
53 return strings.Join(names, ", ")
56// Stop finalizes any tasks in progress, closes network connections, etc.
57func (*Logger) Stop() error {
58 return nil

Notice a couple of things:

  • The module initializer New() receives an instance of output.Params. With this object, the extension can access the output-specific configuration, interfaces to the filesystem, synchronized stdout and stderr, and more.

  • AddMetricSamples in this example writes to stdout. This output might have to be buffered and flushed periodically in a real-world scenario to avoid memory leaks. Below we'll discuss some helpers you can use for that.

Compile your extended k6

To build a k6 binary with this extension, run:

$ xk6 build --with xk6-output-logger=.

xk6-output-logger is the Go module name passed to go mod init

Usually, this would be a URL similar to

Use your extension

Now we can use the extension with a test script.

  1. In new JavaScript file, make some simple test logic.

    1import http from 'k6/http';
    2import { sleep } from 'k6';
    4export default function () {
    5 http.get('');
    6 sleep(0.5);
  2. Now, run the test.

    $ ./k6 run test.js --out logger --quiet --no-summary --iterations 2

The --out logger argument tells k6 to use your custom output. The flag --quiet --no-summary configures k6 to show only custom output.

Your output should look something like this:

2022-07-01T08:55:09.59272-05:00 [http_reqs=1, http_req_duration=117.003, http_req_blocked=558.983, http_req_connecting=54.135, http_req_tls_handshaking=477.198, http_req_sending=0.102, http_req_waiting=116.544, http_req_receiving=0.357, http_req_failed=0]
2022-07-01T08:55:09.917036-05:00 [vus=1, vus_max=1]
2022-07-01T08:55:10.094196-05:00 [data_sent=446, data_received=21364, iteration_duration=1177.505083, iterations=1]
2022-07-01T08:55:10.213926-05:00 [http_reqs=1, http_req_duration=119.122, http_req_blocked=0.015, http_req_connecting=0, http_req_tls_handshaking=0, http_req_sending=0.103, http_req_waiting=118.726, http_req_receiving=0.293, http_req_failed=0]
2022-07-01T08:55:10.715323-05:00 [data_sent=102, data_received=15904, iteration_duration=620.862459, iterations=1]

Things to keep in mind

Questions? Feel free to join the discussion on extensions in the k6 Community Forum.