Skip to content

Commit

Permalink
[anonymizer] Save trace in UI format (#2629)
Browse files Browse the repository at this point in the history
  • Loading branch information
yurishkuro authored Nov 17, 2020
1 parent 72b9a06 commit 008772e
Show file tree
Hide file tree
Showing 13 changed files with 551 additions and 7 deletions.
94 changes: 94 additions & 0 deletions cmd/anonymizer/app/uiconv/extractor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) 2020 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package uiconv

import (
"encoding/json"
"fmt"
"io"
"os"

"go.uber.org/zap"

uimodel "github.com/jaegertracing/jaeger/model/json"
)

// Extractor reads the spans from reader, filters by traceID, and stores as JSON into uiFile.
type Extractor struct {
uiFile *os.File
traceID string
reader *Reader
logger *zap.Logger
}

// NewExtractor creates Extractor.
func NewExtractor(uiFile string, traceID string, reader *Reader, logger *zap.Logger) (*Extractor, error) {
f, err := os.OpenFile(uiFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return nil, fmt.Errorf("cannot create output file: %w", err)
}
logger.Sugar().Infof("Writing spans to UI file %s", uiFile)

return &Extractor{
uiFile: f,
traceID: traceID,
reader: reader,
logger: logger,
}, nil
}

// Run executes the extraction.
func (e *Extractor) Run() error {
e.logger.Info("Parsing captured file for trace", zap.String("trace_id", e.traceID))

var (
spans []uimodel.Span
span *uimodel.Span
err error
)
for span, err = e.reader.NextSpan(); err == nil; span, err = e.reader.NextSpan() {
if string(span.TraceID) == e.traceID {
spans = append(spans, *span)
}
}
if err != io.EOF {
return fmt.Errorf("failed when scanning the file: %w", err)
}
trace := uimodel.Trace{
TraceID: uimodel.TraceID(e.traceID),
Spans: spans,
Processes: make(map[uimodel.ProcessID]uimodel.Process),
}
// (ys) The following is not exactly correct because it does not dedupe the processes,
// but I don't think it affects the UI.
for i := range spans {
span := &spans[i]
pid := uimodel.ProcessID(fmt.Sprintf("p%d", i))
trace.Processes[pid] = *span.Process
span.Process = nil
span.ProcessID = pid
}
jsonBytes, err := json.Marshal(trace)
if err != nil {
return fmt.Errorf("failed to marshal UI trace: %w", err)
}
e.uiFile.Write([]byte(`{"data": [`))
e.uiFile.Write(jsonBytes)
e.uiFile.Write([]byte(`]}`))
e.uiFile.Sync()
e.uiFile.Close()
e.logger.Sugar().Infof("Wrote spans to UI file %s", e.uiFile.Name())
return nil
}
118 changes: 118 additions & 0 deletions cmd/anonymizer/app/uiconv/extractor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright (c) 2020 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package uiconv

import (
"io/ioutil"
"os"
"testing"

"github.com/go-openapi/swag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"

"github.com/jaegertracing/jaeger/model"
)

type UITrace struct {
Data []model.Trace
}

func TestExtractor_TraceSuccess(t *testing.T) {
inputFile := "fixtures/trace_success.json"
outputFile := "fixtures/trace_success_ui_anonymized.json"
defer os.Remove(outputFile)

reader, err := NewReader(
inputFile,
zap.NewNop(),
)
require.NoError(t, err)

extractor, err := NewExtractor(
outputFile,
"2be38093ead7a083",
reader,
zap.NewNop(),
)
require.NoError(t, err)

err = extractor.Run()
require.NoError(t, err)

var trace UITrace
loadJSON(t, outputFile, &trace)

for i := range trace.Data {
for j := range trace.Data[i].Spans {
assert.Equal(t, "span.kind", trace.Data[i].Spans[j].Tags[0].Key)
}
}
}

func TestExtractor_TraceOutputFileError(t *testing.T) {
inputFile := "fixtures/trace_success.json"
outputFile := "fixtures/trace_success_ui_anonymized.json"
defer os.Remove(outputFile)

reader, err := NewReader(
inputFile,
zap.NewNop(),
)
require.NoError(t, err)

err = os.Chmod("fixtures", 0000)
require.NoError(t, err)
defer os.Chmod("fixtures", 0755)

_, err = NewExtractor(
outputFile,
"2be38093ead7a083",
reader,
zap.NewNop(),
)
require.Contains(t, err.Error(), "cannot create output file")
}

func TestExtractor_TraceScanError(t *testing.T) {
inputFile := "fixtures/trace_scan_error.json"
outputFile := "fixtures/trace_scan_error_ui_anonymized.json"
defer os.Remove(outputFile)

reader, err := NewReader(
inputFile,
zap.NewNop(),
)
require.NoError(t, err)

extractor, err := NewExtractor(
outputFile,
"2be38093ead7a083",
reader,
zap.NewNop(),
)
require.NoError(t, err)

err = extractor.Run()
require.Contains(t, err.Error(), "failed when scanning the file")
}

func loadJSON(t *testing.T, fileName string, i interface{}) {
b, err := ioutil.ReadFile(fileName)
require.NoError(t, err)
err = swag.ReadJSON(b, i)
require.NoError(t, err, "Failed to parse json fixture file %s", fileName)
}
Empty file.
2 changes: 2 additions & 0 deletions cmd/anonymizer/app/uiconv/fixtures/trace_invalid_json.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[{"traceID":"2be38093ead7a083","spanID":"7bd66f09ba90ea3d","duration": "invalid"}
]
2 changes: 2 additions & 0 deletions cmd/anonymizer/app/uiconv/fixtures/trace_scan_error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[{"traceID":"2be38093ead7a083","spanID":"7606ddfe69932d34","duration":267037},
]
3 changes: 3 additions & 0 deletions cmd/anonymizer/app/uiconv/fixtures/trace_success.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[{"traceID":"2be38093ead7a083","spanID":"7606ddfe69932d34","flags":1,"operationName":"a071653098f9250d","references":[{"refType":"CHILD_OF","traceID":"2be38093ead7a083","spanID":"492770a15935810f"}],"startTime":1605223981761425,"duration":267037,"tags":[{"key":"span.kind","type":"string","value":"server"}],"logs":[],"process":{"serviceName":"16af988c443cff37","tags":[]},"warnings":null},
{"traceID":"2be38093ead7a083","spanID":"7bd66f09ba90ea3d","flags":1,"operationName":"471418097747d04a","references":[{"refType":"CHILD_OF","traceID":"2be38093ead7a083","spanID":"7606ddfe69932d34"}],"startTime":1605223981965074,"duration":32782,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"error","type":"bool","value":"true"}],"logs":[],"process":{"serviceName":"3c220036602f839e","tags":[]},"warnings":null}
]
1 change: 1 addition & 0 deletions cmd/anonymizer/app/uiconv/fixtures/trace_wrong_format.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"traceID":"2be38093ead7a083","spanID":"7606ddfe69932d34","duration":267037}
40 changes: 40 additions & 0 deletions cmd/anonymizer/app/uiconv/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) 2020 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package uiconv

import (
"go.uber.org/zap"
)

// Config for the extractor.
type Config struct {
CapturedFile string `yaml:"captured_file"`
UIFile string `yaml:"ui_file"`
TraceID string `yaml:"trace_id"`
}

// Extract reads anonymized file, finds spans for a given trace,
// and writes out that trace in the UI format.
func Extract(config Config, logger *zap.Logger) error {
reader, err := NewReader(config.CapturedFile, logger)
if err != nil {
return err
}
ext, err := NewExtractor(config.UIFile, config.TraceID, reader, logger)
if err != nil {
return err
}
return ext.Run()
}
80 changes: 80 additions & 0 deletions cmd/anonymizer/app/uiconv/module_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) 2020 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package uiconv

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)

func TestModule_TraceSuccess(t *testing.T) {
inputFile := "fixtures/trace_success.json"
outputFile := "fixtures/trace_success_ui_anonymized.json"
defer os.Remove(outputFile)

config := Config{
CapturedFile: inputFile,
UIFile: outputFile,
TraceID: "2be38093ead7a083",
}
err := Extract(config, zap.NewNop())
require.NoError(t, err)

var trace UITrace
loadJSON(t, outputFile, &trace)

for i := range trace.Data {
for j := range trace.Data[i].Spans {
assert.Equal(t, "span.kind", trace.Data[i].Spans[j].Tags[0].Key)
}
}
}

func TestModule_TraceNonExistent(t *testing.T) {
inputFile := "fixtures/trace_non_existent.json"
outputFile := "fixtures/trace_non_existent_ui_anonymized.json"
defer os.Remove(outputFile)

config := Config{
CapturedFile: inputFile,
UIFile: outputFile,
TraceID: "2be38093ead7a083",
}
err := Extract(config, zap.NewNop())
require.Contains(t, err.Error(), "cannot open captured file")
}

func TestModule_TraceOutputFileError(t *testing.T) {
inputFile := "fixtures/trace_success.json"
outputFile := "fixtures/trace_success_ui_anonymized.json"
defer os.Remove(outputFile)

config := Config{
CapturedFile: inputFile,
UIFile: outputFile,
TraceID: "2be38093ead7a083",
}

err := os.Chmod("fixtures", 0550)
require.NoError(t, err)
defer os.Chmod("fixtures", 0755)

err = Extract(config, zap.NewNop())
require.Contains(t, err.Error(), "cannot create output file")
}
Loading

0 comments on commit 008772e

Please sign in to comment.