Skip to content

Commit

Permalink
feat(open-telemetry#5408): add otlplogfile exporter
Browse files Browse the repository at this point in the history
This commit adds a new experimental exporter `otlplogfile`, that outputs log records to a JSON line file.
It is based on the following specification: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/file-exporter.md

Signed-off-by: thomasgouveia <[email protected]>
  • Loading branch information
thomasgouveia committed Aug 27, 2024
1 parent f575b5f commit 9b22c0d
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 69 deletions.
33 changes: 10 additions & 23 deletions exporters/otlp/otlplog/otlplogfile/internal/writer/buffered_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"errors"
"fmt"
"io"
"os"
"path"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -18,9 +16,9 @@ import (
const (
msg = "hello, world!"

SizeByte = 1
SizeKiloByte = 1 << (10 * iota)
SizeMegaByte
sizeByte = 1
sizeKiloByte = 1 << (10 * iota)
sizeMegaByte
)

type noopWriteCloser struct {
Expand All @@ -44,32 +42,21 @@ func TestBufferedWrites(t *testing.T) {

var errBenchmark error

func BenchmarkWriter(b *testing.B) {
tempfile := func(tb testing.TB) io.WriteCloser {
f, err := os.CreateTemp(tb.TempDir(), tb.Name())
assert.NoError(tb, err, "must not error when creating benchmark temp file")
tb.Cleanup(func() {
assert.NoError(tb, os.RemoveAll(path.Dir(f.Name())), "must clean up files after being written")
})
return f
}

func BenchmarkBufferedWriter(b *testing.B) {
for _, payloadSize := range []int{
10 * SizeKiloByte,
100 * SizeKiloByte,
SizeMegaByte,
10 * SizeMegaByte,
10 * sizeKiloByte,
100 * sizeKiloByte,
sizeMegaByte,
10 * sizeMegaByte,
} {
payload := make([]byte, payloadSize)
for i := 0; i < payloadSize; i++ {
payload[i] = 'a'
}

for name, w := range map[string]io.WriteCloser{
"discard": &noopWriteCloser{io.Discard},
"buffered-discard": newBufferedWriteCloser(&noopWriteCloser{io.Discard}),
"raw-file": tempfile(b),
"buffered-file": newBufferedWriteCloser(tempfile(b)),
"raw-file": tempFile(b),
"buffered-file": newBufferedWriteCloser(tempFile(b)),
} {
w := w
b.Run(fmt.Sprintf("%s_%d_bytes", name, payloadSize), func(b *testing.B) {
Expand Down
85 changes: 39 additions & 46 deletions exporters/otlp/otlplog/otlplogfile/internal/writer/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,69 +4,62 @@
package writer

import (
"fmt"
"os"
"path"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

const (
jsonLine string = "{\"resourceLogs\":[{\"resource\":{\"attributes\":[{\"key\":\"resource-attr\",\"value\":{\"stringValue\":\"resource-attr-val-1\"}}]},\"scopeLogs\":[{\"scope\":{},\"logRecords\":[{\"timeUnixNano\":\"1581452773000000789\",\"severityNumber\":9,\"severityText\":\"Info\",\"body\":{\"stringValue\":\"This is a log message\"},\"attributes\":[{\"key\":\"app\",\"value\":{\"stringValue\":\"server\"}},{\"key\":\"instance_num\",\"value\":{\"intValue\":\"1\"}}],\"droppedAttributesCount\":1,\"traceId\":\"08040201000000000000000000000000\",\"spanId\":\"0102040800000000\"},{\"timeUnixNano\":\"1581452773000000789\",\"severityNumber\":9,\"severityText\":\"Info\",\"body\":{\"stringValue\":\"something happened\"},\"attributes\":[{\"key\":\"customer\",\"value\":{\"stringValue\":\"acme\"}},{\"key\":\"env\",\"value\":{\"stringValue\":\"dev\"}}],\"droppedAttributesCount\":1,\"traceId\":\"\",\"spanId\":\"\"}]}]}]}"
"github.com/stretchr/testify/require"
)

// tempFile creates a temporary file for the given test case and returns its path on disk.
// The file is automatically cleaned up when the test ends.
func tempFile(tb testing.TB) string {
func tempFile(tb testing.TB) *os.File {
f, err := os.CreateTemp(tb.TempDir(), tb.Name())
assert.NoError(tb, err, "must not error when creating temporary file")
require.NoError(tb, err, "must not error when creating temporary file")
tb.Cleanup(func() {
assert.NoError(tb, os.RemoveAll(path.Dir(f.Name())), "must clean up files after being written")
})
return f.Name()
return f
}

func TestNewFileWriter(t *testing.T) {
f := tempFile(t)

writer, err := NewFileWriter(f.Name(), 0)
defer writer.Shutdown()

assert.NoError(t, err, "must not error when creating the file writer")
assert.Equal(t, f.Name(), writer.path, "writer file path must be the same than the file path")

// Ensure file was created
_, err = os.Stat(f.Name())
assert.NoError(t, err, "must not error when trying to retrieve file stats")
}

func BenchmarkFileWriter(b *testing.B) {
for _, logLines := range []int{
10,
100,
1000,
5000,
} {
logs := make([]string, logLines)
for i := 0; i < logLines; i++ {
logs = append(logs, jsonLine)
}
func TestFileWriterExport(t *testing.T) {
f := tempFile(t)

writer, err := NewFileWriter(f.Name(), 0)
defer writer.Shutdown()
require.NoError(t, err, "must not error when creating the file writer")

for name, interval := range map[string]time.Duration{
"no-flush": 0,
"flush-10ms": 10 * time.Millisecond,
"flush-100ms": 100 * time.Millisecond,
"flush-1s": time.Second,
"flush-10s": 10 * time.Second,
} {
file := tempFile(b)
fw, err := NewFileWriter(file, interval)
assert.NoError(b, err, "must not error when creating FileWriter")
data := []byte("helloworld")
assert.NoError(t, writer.Export(data))

b.Run(fmt.Sprintf("%s_%d_logs", name, logLines), func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
// Force data to be written to disk.
writer.Flush()

// Read file and verify content
content, err := os.ReadFile(f.Name())
require.NoError(t, err, "must not error when reading file content")
assert.Equal(t, "helloworld\n", string(content))
}

for i := 0; i < b.N; i++ {
for _, logLine := range logs {
if err := fw.Export([]byte(logLine)); err != nil {
b.Fatalf("failed to write data: %v", err)
}
}
}
})
func TestFileWriterShutdown(t *testing.T) {
f := tempFile(t)

if err := fw.Shutdown(); err != nil {
b.Fatalf("failed to shutdown FileWriter: %v", err)
}
}
}
writer, err := NewFileWriter(f.Name(), 0)
require.NoError(t, err, "must not error when creating the file writer")
assert.NoError(t, writer.Shutdown(), "must not error when calling Shutdown()")
}

0 comments on commit 9b22c0d

Please sign in to comment.