From 7c5b543f3acf192e75776abc62eda4f6be6ea575 Mon Sep 17 00:00:00 2001 From: danprot Date: Mon, 23 May 2022 17:04:31 +0300 Subject: [PATCH 1/2] big update --- CONTIBUTING.md | 2 +- README.md | 9 +- adapters/fx/fx_adapter.go | 91 ++++ adapters/fx/fx_adapter_test.go | 88 ++++ adapters/fx/runner.go | 98 ++++ .../grpc}/grpc_adapter.go | 42 +- adapters/temporal/adapter_raw_message.go | 49 ++ adapters/temporal/adapter_splitter.go | 58 +++ batcher/batcher.go | 41 +- batcher/batcher_otel.go | 309 ------------ batcher/batcher_test.go | 21 +- batcher/delay_drop.go | 2 +- batcher/exporter_stub.go | 2 +- constants/reserved.go | 7 + constants/tags/readme.md | 5 +- constants/tags/tags.go | 6 +- constants/vals/readme.md | 5 +- constants/vals/vals.go | 6 +- docker-compose.yml | 10 + examples/basic_app/main.go | 30 ++ examples/batcher_tree_test.go | 2 +- examples/component_creation_test.go | 49 ++ examples/many_spans_postgres/main.go | 108 ++++ examples/max_postgres/main.go | 111 +++++ examples/tracers_tree.go | 10 +- exporter.go | 4 +- {golog_exporter => exporters/golog}/go_log.go | 2 +- .../golog}/go_log_test.go | 4 +- .../influxdb18}/influx_exporter.go | 31 +- .../influxdb18}/influx_exporter_mocks.go | 2 +- .../influxdb18}/influx_exporter_test.go | 6 +- exporters/influxdb18/readme.md | 1 + exporters/otel/id_importer.go | 57 +++ exporters/otel/otel_exporter.go | 79 +++ exporters/otel/otel_test.go | 104 ++++ .../otel}/values_mapper.go | 6 +- exporters/postgres/connection.go | 21 + .../postgres}/pg_exporter.go | 317 +++++++----- .../postgres}/pg_exporter_test.go | 45 +- exporters/postgres/pgconnector/pg.sql | 23 + .../postgres/pgconnector/pg_example_test.go | 61 +++ .../pgconnector/pg_integration_test.go | 295 +++++++++++ .../pgconnector/pg_testfixture_test.go | 58 +++ exporters/postgres/pgconnector/pgconnector.go | 85 ++++ .../postgres}/postgres_types.go | 40 +- {pg_exporter => exporters/postgres}/readme.md | 0 .../postgres}/table_schema.go | 29 +- exporters/spancollector/collector.go | 24 + exporters/testlog/test_log_exporter.go | 27 + factory.go | 59 ++- factory_test.go | 13 +- fx_adapter/fx_adapter.go | 81 --- go.mod | 30 +- go.sum | 468 +++++++++++++++++- influx_exporter/readme.md | 1 - NOTICE.txt => legal_notices.txt | 212 ++++++-- otel_exporter/otel_exporter_to_tracer.go | 57 --- otel_exporter/otel_test.go | 34 -- pg_exporter/pg_conn/pg.sh | 1 - pg_exporter/pg_conn/pg.sql | 4 - pg_exporter/pg_conn/pg_conn.go | 33 -- pg_exporter/pg_conn/pg_integration_test.go | 333 ------------- pg_exporter/pg_conn/pg_testfixture_test.go | 34 -- pg_exporter/pg_exporter_mock.go | 143 ------ span.go | 172 +++++-- span_context.go | 6 +- span_opt.go | 87 ++++ span_test.go | 97 +++- temporal_adapter/logger_v2.go | 50 -- test_log_exporter/test_log_exporter.go | 26 - testdata/testdata.go | 2 +- traceid.go | 189 +++++-- traceid_test.go | 94 ++++ util/errs/errs.go | 4 +- util/reflectutil/reflectutil.go | 3 +- util/stringutil/stringutil.go | 3 +- util/testutil/env.go | 2 + util/testutil/hostname.go | 11 - util/testutil/timeout.go | 2 +- utils.go | 15 +- utils_test.go | 4 +- 81 files changed, 3147 insertions(+), 1605 deletions(-) create mode 100644 adapters/fx/fx_adapter.go create mode 100644 adapters/fx/fx_adapter_test.go create mode 100644 adapters/fx/runner.go rename {grpc_adapter => adapters/grpc}/grpc_adapter.go (57%) create mode 100644 adapters/temporal/adapter_raw_message.go create mode 100644 adapters/temporal/adapter_splitter.go delete mode 100644 batcher/batcher_otel.go create mode 100644 constants/reserved.go create mode 100644 docker-compose.yml create mode 100644 examples/basic_app/main.go create mode 100644 examples/component_creation_test.go create mode 100644 examples/many_spans_postgres/main.go create mode 100644 examples/max_postgres/main.go rename {golog_exporter => exporters/golog}/go_log.go (95%) rename {golog_exporter => exporters/golog}/go_log_test.go (82%) rename {influx_exporter => exporters/influxdb18}/influx_exporter.go (84%) rename {influx_exporter => exporters/influxdb18}/influx_exporter_mocks.go (98%) rename {influx_exporter => exporters/influxdb18}/influx_exporter_test.go (93%) create mode 100644 exporters/influxdb18/readme.md create mode 100644 exporters/otel/id_importer.go create mode 100644 exporters/otel/otel_exporter.go create mode 100644 exporters/otel/otel_test.go rename {otel_exporter => exporters/otel}/values_mapper.go (87%) create mode 100644 exporters/postgres/connection.go rename {pg_exporter => exporters/postgres}/pg_exporter.go (55%) rename {pg_exporter => exporters/postgres}/pg_exporter_test.go (58%) create mode 100644 exporters/postgres/pgconnector/pg.sql create mode 100644 exporters/postgres/pgconnector/pg_example_test.go create mode 100644 exporters/postgres/pgconnector/pg_integration_test.go create mode 100644 exporters/postgres/pgconnector/pg_testfixture_test.go create mode 100644 exporters/postgres/pgconnector/pgconnector.go rename {pg_exporter => exporters/postgres}/postgres_types.go (62%) rename {pg_exporter => exporters/postgres}/readme.md (100%) rename {pg_exporter => exporters/postgres}/table_schema.go (79%) create mode 100644 exporters/spancollector/collector.go create mode 100644 exporters/testlog/test_log_exporter.go delete mode 100644 fx_adapter/fx_adapter.go delete mode 100644 influx_exporter/readme.md rename NOTICE.txt => legal_notices.txt (81%) delete mode 100644 otel_exporter/otel_exporter_to_tracer.go delete mode 100644 otel_exporter/otel_test.go delete mode 100644 pg_exporter/pg_conn/pg.sh delete mode 100644 pg_exporter/pg_conn/pg.sql delete mode 100644 pg_exporter/pg_conn/pg_conn.go delete mode 100644 pg_exporter/pg_conn/pg_integration_test.go delete mode 100644 pg_exporter/pg_conn/pg_testfixture_test.go delete mode 100644 pg_exporter/pg_exporter_mock.go create mode 100644 span_opt.go delete mode 100644 temporal_adapter/logger_v2.go delete mode 100644 test_log_exporter/test_log_exporter.go create mode 100644 traceid_test.go delete mode 100644 util/testutil/hostname.go diff --git a/CONTIBUTING.md b/CONTIBUTING.md index 6880843..4e899ae 100644 --- a/CONTIBUTING.md +++ b/CONTIBUTING.md @@ -7,4 +7,4 @@ 6. Run code coverage to check if the lines of code you added are covered by unit tests. 7. Once your feature is complete, prepare the commit with appropriate message and the issue number. 8. Create a [pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) and wait for the users to review. When you submit a pull request, please, agree to the terms of [CLA](https://github.com/KasperskyLab/klig/blob/master/CLA.md). -9. Once everything is done, your pull request gets merged. Your feature will be available with the next release and your name will be added to [AUTHORS](https://github.com/KasperskyLab/klig/blob/master/AUTHORS.md). +9. Once everything is done, your pull request gets merged. Your feature will be available with the next release and your name will be added to [AUTHORS](https://github.com/KasperskyLab/klogga/blob/master/AUTHORS.md). diff --git a/README.md b/README.md index f6f72df..979afd0 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,5 @@ Data collected via klogga can be configured to be exported to different sources, # Getting Started import klogga -cofigure your root tracer - -# Build and Test -go test ./... - -# Contribute -ask Danila +configure your root tracer, don't forget to use batcher for buffered traces +see examples/tracers_tree.go diff --git a/adapters/fx/fx_adapter.go b/adapters/fx/fx_adapter.go new file mode 100644 index 0000000..347e500 --- /dev/null +++ b/adapters/fx/fx_adapter.go @@ -0,0 +1,91 @@ +package fx + +import ( + "context" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/exporters/golog" + "go.uber.org/fx" + "go.uber.org/fx/fxevent" + "reflect" + "strings" +) + +const Component klogga.ComponentName = "fx" + +type fxTracer struct { + trs klogga.Tracer +} + +func (t fxTracer) LogEvent(event fxevent.Event) { + span := klogga.StartLeaf(context.Background()) + defer span.FlushTo(t.trs) + span.Tag("event", reflect.TypeOf(event).String()) + switch e := event.(type) { + case *fxevent.OnStartExecuting: + span.OverrideName(e.CallerName).Tag("callee", e.FunctionName) + case *fxevent.OnStartExecuted: + span.OverrideName(e.CallerName).Tag("callee", e.FunctionName) + if e.Err != nil { + span.ErrVoid(e.Err) + } else { + span.Message("runtime:" + e.Runtime.String()) + } + case *fxevent.OnStopExecuting: + span.OverrideName(e.CallerName).Tag("callee", e.FunctionName) + case *fxevent.OnStopExecuted: + span.OverrideName(e.CallerName).Tag("callee", e.FunctionName) + if e.Err != nil { + span.ErrVoid(e.Err) + } else { + span.Message("runtime:" + e.Runtime.String()) + } + case *fxevent.Supplied: + span.OverrideName("Supplied").Message("supplied type:" + e.TypeName).ErrVoid(e.Err) + case *fxevent.Provided: + span.OverrideName("Provided"). + Message("output types:" + strings.Join(e.OutputTypeNames, ",")).ErrVoid(e.Err) + case *fxevent.Invoking: + span.OverrideName(e.FunctionName) + case *fxevent.Invoked: + span.OverrideName(e.FunctionName) + if e.Err != nil { + span.ErrSpan(e.Err).Message("stack:" + e.Trace) + } + case *fxevent.Stopping: + span.OverrideName("Stopping").Message("signal " + strings.ToUpper(e.Signal.String())) + case *fxevent.Stopped: + span.OverrideName("Stopped").ErrVoid(e.Err) + case *fxevent.RollingBack: + span.OverrideName("RollingBack").ErrVoid(e.StartErr) + case *fxevent.RolledBack: + span.OverrideName("RolledBack").ErrVoid(e.Err) + case *fxevent.Started: + span.OverrideName("Started").ErrVoid(e.Err) + case *fxevent.LoggerInitialized: + span.OverrideName(e.ConstructorName).ErrVoid(e.Err) + } +} + +// Module send fx logs to standard tracer +func Module(tf klogga.TracerProvider) fx.Option { + fxTrs := &fxTracer{trs: tf.Named(Component)} + return fx.Options( + fx.WithLogger( + func() (fxevent.Logger, error) { + return fxTrs, nil + }, + ), + ) +} + +// Full Set up the default logging for the app +// registering logging and the klogga factory, +// that later can be reconfigured with more loggers +func Full() fx.Option { + tf := klogga.NewFactory(golog.New(nil)) + return fx.Options( + Module(tf), + fx.Supply(tf), + fx.Provide(func(tf *klogga.Factory) klogga.TracerProvider { return tf }), + ) +} diff --git a/adapters/fx/fx_adapter_test.go b/adapters/fx/fx_adapter_test.go new file mode 100644 index 0000000..812732b --- /dev/null +++ b/adapters/fx/fx_adapter_test.go @@ -0,0 +1,88 @@ +package fx + +import ( + "context" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/exporters/golog" + "github.com/KasperskyLab/klogga/exporters/spancollector" + "github.com/KasperskyLab/klogga/util/testutil" + "github.com/stretchr/testify/require" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" + "testing" +) + +func TestFxAdapter(t *testing.T) { + collector := spancollector.SpanCollector{} + tf := klogga.NewFactory(&collector, golog.New(nil)) + app := fx.New( + Module(tf), + fx.Invoke(RunTestInvoke), + ) + err := app.Start(testutil.Timeout()) + require.NoError(t, err) + err = app.Stop(testutil.Timeout()) + require.NoError(t, err) + + require.Contains(t, collector.Spans[3].Stringify(), "LoggerInitialized") + require.Contains(t, collector.Spans[4].Stringify(), "RunTestInvoke") + require.Contains(t, collector.Spans[5].Stringify(), "RunTestInvoke") + require.Contains(t, collector.Spans[6].Stringify(), "StartSomething") + + require.Contains(t, collector.Spans[9].Stringify(), "StopSomething") +} + +func RunTestInvoke(lf fx.Lifecycle) { + lf.Append( + fx.Hook{ + OnStart: StartSomething, + OnStop: StopSomething, + }, + ) +} + +func StartSomething(context.Context) error { return nil } +func StopSomething(context.Context) error { return nil } + +func TestFullModule(t *testing.T) { + app := fx.New( + Full(), + fx.Invoke( + func(tf *klogga.Factory) { + + }, + ), + ) + err := app.Start(testutil.Timeout()) + require.NoError(t, err) + err = app.Stop(testutil.Timeout()) + require.NoError(t, err) +} + +type testRunner struct { + Started, Stopped int +} + +func (t *testRunner) Start(context.Context) error { + t.Started++ + return nil +} + +func (t *testRunner) Stop(context.Context) error { + t.Stopped++ + return nil +} + +func TestRunnersGroup(t *testing.T) { + + tr := &testRunner{} + + app := fxtest.New( + t, + fx.Provide(fx.Annotate(func() *testRunner { return tr }, TagRunner...)), + fx.Invoke(RegisterRunners), + ) + app.RequireStart().RequireStop() + require.Equal(t, 1, tr.Started) + require.Equal(t, 1, tr.Stopped) +} diff --git a/adapters/fx/runner.go b/adapters/fx/runner.go new file mode 100644 index 0000000..5e1db34 --- /dev/null +++ b/adapters/fx/runner.go @@ -0,0 +1,98 @@ +package fx + +import ( + "context" + "fmt" + "github.com/KasperskyLab/klogga" + "go.uber.org/fx" + "reflect" +) + +// Runner simplifies registration of components that should start and stop +type Runner interface { + // Start starts the runner, start process can timeout by context + // runner is responsible for creating goroutines etc + // should not block and exit when startup is done + Start(ctx context.Context) error + + // Stop stops the runner, the stop process can timeout by contest + Stop(ctx context.Context) error +} + +const RunnersGroupName = "runners" + +var RunnersGroupAttribute = fmt.Sprintf(`group:"%s"`, RunnersGroupName) +var ResultTagRunner = fx.ResultTags(RunnersGroupAttribute) +var TagRunner = []fx.Annotation{fx.As(new(Runner)), ResultTagRunner} + +type RunnersGroup struct { + fx.In + Runners []Runner `group:"runners"` +} + +func RegisterRunners(r RunnersGroup, lc fx.Lifecycle) { + r.Register(lc) +} + +func (rr RunnersGroup) Register(lc fx.Lifecycle) { + for _, runner := range rr.Runners { + lc.Append(ToHook(runner)) + } +} + +// RegisterRunnersWithErrors experimental watch for runner errors via channel +func RegisterRunnersWithErrors(r RunnersGroup, tf klogga.TracerProvider, lc fx.Lifecycle, s fx.Shutdowner) { + r.RegisterWithErrors(tf, lc, s) +} + +func (rr RunnersGroup) RegisterWithErrors(tf klogga.TracerProvider, lc fx.Lifecycle, s fx.Shutdowner) { + trs := tf.NamedPkg() + for _, runner := range rr.Runners { + re, ok := runner.(RunnerErr) + if !ok { + lc.Append(ToHook(runner)) + continue + } + ctx, cancelFunc := context.WithCancel(context.Background()) + lc.Append(ToHookWithCtx(runner, cancelFunc)) + go func(r Runner) { + select { + case err := <-re.Error(): + klogga.Message("runner error"). + Tag("runner", reflect.TypeOf(r).String()). + ErrSpan(err).FlushTo(trs) + case <-ctx.Done(): + + } + + if err := s.Shutdown(); err != nil { + klogga.Message("shutdowner error"). + Tag("runner", reflect.TypeOf(r).String()). + ErrSpan(err).FlushTo(trs) + + } + }(runner) + } +} + +func ToHook(r Runner) fx.Hook { + return fx.Hook{ + OnStart: r.Start, + OnStop: r.Stop, + } +} + +func ToHookWithCtx(r Runner, onStopped context.CancelFunc) fx.Hook { + return fx.Hook{ + OnStart: r.Start, + OnStop: func(ctx context.Context) error { + err := r.Stop(ctx) + onStopped() + return err + }, + } +} + +type RunnerErr interface { + Error() <-chan error +} diff --git a/grpc_adapter/grpc_adapter.go b/adapters/grpc/grpc_adapter.go similarity index 57% rename from grpc_adapter/grpc_adapter.go rename to adapters/grpc/grpc_adapter.go index 869f549..4bfb188 100644 --- a/grpc_adapter/grpc_adapter.go +++ b/adapters/grpc/grpc_adapter.go @@ -1,117 +1,117 @@ -package grpc_adapter +package grpc import ( "context" "fmt" - "go.kl/klogga" + "github.com/KasperskyLab/klogga" ) -type GrpcLoggerV2Adapter struct { +type LoggerV2 struct { trs klogga.Tracer - conf *GrpcLoggerConf + conf *Conf } -type GrpcLoggerConf struct { +type Conf struct { // 0 - log everything // 4 - log nothing VerbosityLevel klogga.LogLevel } -func NewGrpcLoggerV2Adapter(trs klogga.Factory, conf *GrpcLoggerConf) *GrpcLoggerV2Adapter { - return &GrpcLoggerV2Adapter{trs: trs.Named("grpc"), conf: conf} +func NewLoggerV2(trs klogga.TracerProvider, conf *Conf) *LoggerV2 { + return &LoggerV2{trs: trs.Named("grpc"), conf: conf} } -func (g GrpcLoggerV2Adapter) trace(level klogga.LogLevel, mes string) { +func (g LoggerV2) trace(level klogga.LogLevel, mes string) { span := klogga.StartLeaf(context.Background()) defer g.trs.Finish(span) span.Level(level).Val("message", mes) } -func (g GrpcLoggerV2Adapter) Info(args ...interface{}) { +func (g LoggerV2) Info(args ...interface{}) { if g.conf.VerbosityLevel <= klogga.Info { return } g.trace(klogga.Info, fmt.Sprint(args...)) } -func (g GrpcLoggerV2Adapter) Infoln(args ...interface{}) { +func (g LoggerV2) Infoln(args ...interface{}) { if g.conf.VerbosityLevel <= klogga.Info { return } g.trace(klogga.Info, fmt.Sprint(args...)) } -func (g GrpcLoggerV2Adapter) Infof(format string, args ...interface{}) { +func (g LoggerV2) Infof(format string, args ...interface{}) { if g.conf.VerbosityLevel <= klogga.Info { return } g.trace(klogga.Info, fmt.Sprintf(format, args...)) } -func (g GrpcLoggerV2Adapter) Warning(args ...interface{}) { +func (g LoggerV2) Warning(args ...interface{}) { if g.conf.VerbosityLevel <= klogga.Warn { return } g.trace(klogga.Warn, fmt.Sprint(args...)) } -func (g GrpcLoggerV2Adapter) Warningln(args ...interface{}) { +func (g LoggerV2) Warningln(args ...interface{}) { if g.conf.VerbosityLevel <= klogga.Warn { return } g.trace(klogga.Warn, fmt.Sprint(args...)) } -func (g GrpcLoggerV2Adapter) Warningf(format string, args ...interface{}) { +func (g LoggerV2) Warningf(format string, args ...interface{}) { if g.conf.VerbosityLevel <= klogga.Warn { return } g.trace(klogga.Warn, fmt.Sprintf(format, args...)) } -func (g GrpcLoggerV2Adapter) Error(args ...interface{}) { +func (g LoggerV2) Error(args ...interface{}) { if g.conf.VerbosityLevel <= klogga.Error { return } g.trace(klogga.Error, fmt.Sprint(args...)) } -func (g GrpcLoggerV2Adapter) Errorln(args ...interface{}) { +func (g LoggerV2) Errorln(args ...interface{}) { if g.conf.VerbosityLevel <= klogga.Error { return } g.trace(klogga.Error, fmt.Sprint(args...)) } -func (g GrpcLoggerV2Adapter) Errorf(format string, args ...interface{}) { +func (g LoggerV2) Errorf(format string, args ...interface{}) { if g.conf.VerbosityLevel <= klogga.Error { return } g.trace(klogga.Error, fmt.Sprintf(format, args...)) } -func (g GrpcLoggerV2Adapter) Fatal(args ...interface{}) { +func (g LoggerV2) Fatal(args ...interface{}) { if g.conf.VerbosityLevel <= klogga.Fatal { return } g.trace(klogga.Fatal, fmt.Sprint(args...)) } -func (g GrpcLoggerV2Adapter) Fatalln(args ...interface{}) { +func (g LoggerV2) Fatalln(args ...interface{}) { if g.conf.VerbosityLevel <= klogga.Fatal { return } g.trace(klogga.Fatal, fmt.Sprint(args...)) } -func (g GrpcLoggerV2Adapter) Fatalf(format string, args ...interface{}) { +func (g LoggerV2) Fatalf(format string, args ...interface{}) { if g.conf.VerbosityLevel <= klogga.Fatal { return } g.trace(klogga.Fatal, fmt.Sprintf(format, args...)) } -func (g GrpcLoggerV2Adapter) V(l int) bool { +func (g LoggerV2) V(l int) bool { if g.conf.VerbosityLevel == 0 { return true } diff --git a/adapters/temporal/adapter_raw_message.go b/adapters/temporal/adapter_raw_message.go new file mode 100644 index 0000000..5146570 --- /dev/null +++ b/adapters/temporal/adapter_raw_message.go @@ -0,0 +1,49 @@ +package temporal + +import ( + "context" + "fmt" + "github.com/KasperskyLab/klogga" +) + +// AdapterRawMessage basic adapter of temporal logger to spans +// treats keyvals as default logger does i.e. as a Sprint parameters +// therefore, no structure is assumed +// implements +// type Logger interface { +// Debug(msg string, keyvals ...interface{}) +// Info(msg string, keyvals ...interface{}) +// Warn(msg string, keyvals ...interface{}) +// Error(msg string, keyvals ...interface{}) +// } +// without the dependency, so be warned + +type AdapterRawMessage struct { + trs klogga.Tracer +} + +func NewAdapterRawMessage(trs klogga.TracerProvider) *AdapterSplitter { + return &AdapterSplitter{trs: trs.Named("temporal")} +} + +func (t *AdapterRawMessage) trace(level klogga.LogLevel, msg string, keyvals ...interface{}) { + span := klogga.StartLeaf(context.Background()).Level(level) + defer t.trs.Finish(span) + span.Message(fmt.Sprint(append([]interface{}{level, msg}, keyvals...))) +} + +func (t *AdapterRawMessage) Debug(msg string, keyvals ...interface{}) { + t.trace(klogga.Info, msg, keyvals...) +} + +func (t *AdapterRawMessage) Info(msg string, keyvals ...interface{}) { + t.trace(klogga.Info, msg, keyvals...) +} + +func (t *AdapterRawMessage) Warn(msg string, keyvals ...interface{}) { + t.trace(klogga.Warn, msg, keyvals...) +} + +func (t *AdapterRawMessage) Error(msg string, keyvals ...interface{}) { + t.trace(klogga.Error, msg, keyvals...) +} diff --git a/adapters/temporal/adapter_splitter.go b/adapters/temporal/adapter_splitter.go new file mode 100644 index 0000000..429e234 --- /dev/null +++ b/adapters/temporal/adapter_splitter.go @@ -0,0 +1,58 @@ +package temporal + +import ( + "context" + "fmt" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/util/stringutil" +) + +// AdapterSplitter basic adapter of temporal logger to spans +// assumes that keyvals are (key,value) pairs to have some semblance of structure +// implements +// type Logger interface { +// Debug(msg string, keyvals ...interface{}) +// Info(msg string, keyvals ...interface{}) +// Warn(msg string, keyvals ...interface{}) +// Error(msg string, keyvals ...interface{}) +// } +// without the dependency, so be warned +type AdapterSplitter struct { + trs klogga.Tracer +} + +func NewAdapterSplitter(trs klogga.TracerProvider) *AdapterSplitter { + return &AdapterSplitter{trs: trs.Named("temporal")} +} + +func (t *AdapterSplitter) trace(level klogga.LogLevel, msg string, keyvals ...interface{}) { + span := klogga.StartLeaf(context.Background()).Level(level) + defer t.trs.Finish(span) + + var key string + for i, keyVal := range keyvals { + if i%2 == 0 { + key = fmt.Sprintf("%v", keyVal) + key = stringutil.ToSnakeCase(key) + continue + } + span.Val(key, keyVal) + } + span.Message(msg) +} + +func (t *AdapterSplitter) Debug(msg string, keyvals ...interface{}) { + t.trace(klogga.Info, msg, keyvals...) +} + +func (t *AdapterSplitter) Info(msg string, keyvals ...interface{}) { + t.trace(klogga.Info, msg, keyvals...) +} + +func (t *AdapterSplitter) Warn(msg string, keyvals ...interface{}) { + t.trace(klogga.Warn, msg, keyvals...) +} + +func (t *AdapterSplitter) Error(msg string, keyvals ...interface{}) { + t.trace(klogga.Error, msg, keyvals...) +} diff --git a/batcher/batcher.go b/batcher/batcher.go index 311067b..e5dd11e 100644 --- a/batcher/batcher.go +++ b/batcher/batcher.go @@ -2,19 +2,23 @@ package batcher import ( "context" - "go.kl/klogga" - "go.kl/klogga/util/errs" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/util/errs" "sync" + "sync/atomic" "time" ) -type batcher struct { +// Batcher collects spans to send them to exporters in batches +type Batcher struct { exporter klogga.Exporter conf Config spans chan *klogga.Span - cond *sync.Cond + flushedCount uint64 + erredCount uint64 + cond *sync.Cond tm *time.Timer stop chan struct{} @@ -47,8 +51,10 @@ func ConfigDefault() Config { } } -func New(exporter klogga.Exporter, conf Config) *batcher { - b := &batcher{ +// New constructs and starts the Batcher +// beware that errors from the exporter are ignored, so if you really need them use a decorator or something +func New(exporter klogga.Exporter, conf Config) *Batcher { + b := &Batcher{ exporter: exporter, conf: conf, spans: make(chan *klogga.Span, conf.GetBufferSize()), @@ -60,7 +66,15 @@ func New(exporter klogga.Exporter, conf Config) *batcher { return b } -func (b *batcher) start() { +func (b *Batcher) FlushedCount() (res uint64) { + return atomic.LoadUint64(&b.flushedCount) +} + +func (b *Batcher) ErredCount() (res uint64) { + return atomic.LoadUint64(&b.erredCount) +} + +func (b *Batcher) start() { b.tm = time.AfterFunc( b.conf.Timeout, func() { b.cond.Signal() @@ -75,7 +89,12 @@ func (b *batcher) start() { case span := <-b.spans: toFlush = append(toFlush, span) if len(b.spans) == 0 || len(toFlush) >= b.conf.BatchSize { - _ = b.exporter.Write(context.Background(), toFlush) + err := b.exporter.Write(context.Background(), toFlush) + if err != nil { + atomic.AddUint64(&b.erredCount, uint64(len(toFlush))) + } else { + atomic.AddUint64(&b.flushedCount, uint64(len(toFlush))) + } toFlush = toFlush[:0] continue } @@ -97,7 +116,7 @@ func (b *batcher) start() { }() } -func (b *batcher) Write(ctx context.Context, spans []*klogga.Span) error { +func (b *Batcher) Write(ctx context.Context, spans []*klogga.Span) error { for _, span := range spans { select { case b.spans <- span: @@ -111,7 +130,7 @@ func (b *batcher) Write(ctx context.Context, spans []*klogga.Span) error { return nil } -func (b *batcher) Shutdown(ctx context.Context) (err error) { +func (b *Batcher) Shutdown(ctx context.Context) (err error) { b.TriggerFlush() select { case b.stop <- struct{}{}: @@ -122,6 +141,6 @@ func (b *batcher) Shutdown(ctx context.Context) (err error) { } // TriggerFlush asynchronously writes queue content to writer -func (b *batcher) TriggerFlush() { +func (b *Batcher) TriggerFlush() { b.cond.Signal() } diff --git a/batcher/batcher_otel.go b/batcher/batcher_otel.go deleted file mode 100644 index 03b7487..0000000 --- a/batcher/batcher_otel.go +++ /dev/null @@ -1,309 +0,0 @@ -package batcher - -import ( - "context" - "go.kl/klogga" - "sync" - "sync/atomic" - "time" - - "go.opentelemetry.io/otel" -) - -// batcher implementation from OTEL -// trying to convert it - -// Defaults for BatchSpanProcessorOptions. -const ( - DefaultMaxQueueSize = 2048 - DefaultBatchTimeout = 5000 * time.Millisecond - DefaultExportTimeout = 30000 * time.Millisecond - DefaultMaxExportBatchSize = 512 -) - -type BatchSpanProcessorOption func(o *BatchSpanProcessorOptions) - -type BatchSpanProcessorOptions struct { - // MaxQueueSize is the maximum queue size to buffer spans for delayed processing. If the - // queue gets full it drops the spans. Use BlockOnQueueFull to change this behavior. - // The default value of MaxQueueSize is 2048. - MaxQueueSize int - - // BatchTimeout is the maximum duration for constructing a batch. Processor - // forcefully sends available spans when timeout is reached. - // The default value of BatchTimeout is 5000 msec. - BatchTimeout time.Duration - - // ExportTimeout specifies the maximum duration for exporting spans. If the timeout - // is reached, the export will be cancelled. - // The default value of ExportTimeout is 30000 msec. - ExportTimeout time.Duration - - // MaxExportBatchSize is the maximum number of spans to process in a single batch. - // If there are more than one batch worth of spans then it processes multiple batches - // of spans one batch after the other without any delay. - // The default value of MaxExportBatchSize is 512. - MaxExportBatchSize int - - // BlockOnQueueFull blocks onEnd() and onStart() method if the queue is full - // AND if BlockOnQueueFull is set to true. - // Blocking option should be used carefully as it can severely affect the performance of an - // application. - BlockOnQueueFull bool -} - -// otelBatcher batches asynchronously-received -// spans and sends them to a Batchable tracer when complete. -type otelBatcher struct { - e klogga.Exporter - o BatchSpanProcessorOptions - - queue chan *klogga.Span - dropped uint32 - - batch []*klogga.Span - batchMutex sync.Mutex - timer *time.Timer - stopWait sync.WaitGroup - stopOnce sync.Once - stopCh chan struct{} -} - -var _ klogga.Exporter = (*otelBatcher)(nil) - -// NewOtelBatcher creates a new SpanProcessor that will send completed -// span batches to the exporter with the supplied options. -// -// If the exporter is nil, the span processor will preform no action. -func NewOtelBatcher(exporter klogga.Exporter, options ...BatchSpanProcessorOption) klogga.Exporter { - o := BatchSpanProcessorOptions{ - BatchTimeout: DefaultBatchTimeout, - ExportTimeout: DefaultExportTimeout, - MaxQueueSize: DefaultMaxQueueSize, - MaxExportBatchSize: DefaultMaxExportBatchSize, - } - for _, opt := range options { - opt(&o) - } - bsp := &otelBatcher{ - e: exporter, - o: o, - batch: make([]*klogga.Span, 0, o.MaxExportBatchSize), - timer: time.NewTimer(o.BatchTimeout), - queue: make(chan *klogga.Span, o.MaxQueueSize), - stopCh: make(chan struct{}), - } - - bsp.stopWait.Add(1) - go func() { - defer bsp.stopWait.Done() - bsp.processQueue() - bsp.drainQueue() - }() - - return bsp -} - -// Shutdown flushes the queue and waits until all spans are processed. -// It only executes once. Subsequent call does nothing. -func (bsp *otelBatcher) Shutdown(ctx context.Context) error { - var err error - bsp.stopOnce.Do( - func() { - wait := make(chan struct{}) - go func() { - close(bsp.stopCh) - bsp.stopWait.Wait() - if bsp.e != nil { - if err := bsp.e.Shutdown(ctx); err != nil { - otel.Handle(err) - } - } - close(wait) - }() - // Wait until the wait group is done or the context is cancelled - select { - case <-wait: - case <-ctx.Done(): - err = ctx.Err() - } - }, - ) - return err -} - -// ForceFlush exports all ended spans that have not yet been exported. -func (bsp *otelBatcher) ForceFlush(ctx context.Context) error { - var err error - if bsp.e != nil { - wait := make(chan error) - go func() { - wait <- bsp.exportSpans(ctx) - close(wait) - }() - // Wait until the export is finished or the context is cancelled/timed out - select { - case err = <-wait: - case <-ctx.Done(): - err = ctx.Err() - } - } - return err -} - -func WithMaxQueueSize(size int) BatchSpanProcessorOption { - return func(o *BatchSpanProcessorOptions) { - o.MaxQueueSize = size - } -} - -func WithMaxExportBatchSize(size int) BatchSpanProcessorOption { - return func(o *BatchSpanProcessorOptions) { - o.MaxExportBatchSize = size - } -} - -func WithBatchTimeout(delay time.Duration) BatchSpanProcessorOption { - return func(o *BatchSpanProcessorOptions) { - o.BatchTimeout = delay - } -} - -func WithExportTimeout(timeout time.Duration) BatchSpanProcessorOption { - return func(o *BatchSpanProcessorOptions) { - o.ExportTimeout = timeout - } -} - -func WithBlocking() BatchSpanProcessorOption { - return func(o *BatchSpanProcessorOptions) { - o.BlockOnQueueFull = true - } -} - -// exportSpans is a subroutine of processing and draining the queue. -func (bsp *otelBatcher) exportSpans(ctx context.Context) error { - bsp.timer.Reset(bsp.o.BatchTimeout) - - bsp.batchMutex.Lock() - defer bsp.batchMutex.Unlock() - - if bsp.o.ExportTimeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, bsp.o.ExportTimeout) - defer cancel() - } - - if l := len(bsp.batch); l > 0 { - bsp.e.Write(ctx, bsp.batch) - // A new batch is always created after exporting, even if the batch failed to be exported. - // - // It is up to the exporter to implement any type of retry logic if a batch is failing - // to be exported, since it is specific to the protocol and backend being sent to. - bsp.batch = bsp.batch[:0] - } - return nil -} - -// processQueue removes spans from the `queue` channel until processor -// is shut down. It calls the exporter in batches of up to MaxExportBatchSize -// waiting up to BatchTimeout to form a batch. -func (bsp *otelBatcher) processQueue() { - defer bsp.timer.Stop() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - for { - select { - case <-bsp.stopCh: - return - case <-bsp.timer.C: - if err := bsp.exportSpans(ctx); err != nil { - otel.Handle(err) - } - case sd := <-bsp.queue: - bsp.batchMutex.Lock() - bsp.batch = append(bsp.batch, sd) - shouldExport := len(bsp.batch) >= bsp.o.MaxExportBatchSize - bsp.batchMutex.Unlock() - if shouldExport { - if !bsp.timer.Stop() { - <-bsp.timer.C - } - if err := bsp.exportSpans(ctx); err != nil { - otel.Handle(err) - } - } - } - } -} - -// drainQueue awaits the any caller that had added to bsp.stopWait -// to finish the enqueue, then exports the final batch. -func (bsp *otelBatcher) drainQueue() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - for { - select { - case sd := <-bsp.queue: - if sd == nil { - if err := bsp.exportSpans(ctx); err != nil { - otel.Handle(err) - } - return - } - - bsp.batchMutex.Lock() - bsp.batch = append(bsp.batch, sd) - shouldExport := len(bsp.batch) == bsp.o.MaxExportBatchSize - bsp.batchMutex.Unlock() - - if shouldExport { - if err := bsp.exportSpans(ctx); err != nil { - otel.Handle(err) - } - } - default: - close(bsp.queue) - } - } -} - -func (bsp *otelBatcher) Write(ctx context.Context, spans []*klogga.Span) error { - for _, span := range spans { - - // This ensures the bsp.queue<- below does not panic as the - // processor shuts down. - //defer func() { - // x := recover() - // switch err := x.(type) { - // case nil: - // return - // case runtime.Error: - // if err.Error() == "send on closed channel" { - // return - // } - // } - // panic(x) - //}() - - select { - case <-bsp.stopCh: - return nil - default: - } - - if bsp.o.BlockOnQueueFull { - bsp.queue <- span - continue - } - - select { - case bsp.queue <- span: - default: - atomic.AddUint32(&bsp.dropped, 1) - } - - } - return nil -} diff --git a/batcher/batcher_test.go b/batcher/batcher_test.go index 4a1b8bb..3462a08 100644 --- a/batcher/batcher_test.go +++ b/batcher/batcher_test.go @@ -1,9 +1,9 @@ package batcher import ( + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/util/testutil" "github.com/stretchr/testify/require" - "go.kl/klogga" - "go.kl/klogga/util/testutil" "testing" "time" ) @@ -15,16 +15,16 @@ func TestBatches(t *testing.T) { sps = append(sps, klogga.StartLeaf(testutil.Timeout())) } exporter := &exporterStub{} - batcher := New(exporter, ConfigDefault()) - trs := klogga.NewFactory(batcher).NamedPkg() + bb := New(exporter, ConfigDefault()) + trs := klogga.NewFactory(bb).NamedPkg() for _, sp := range sps { trs.Finish(sp) } - batcher.TriggerFlush() - - time.Sleep(100 * time.Millisecond) + err := bb.Shutdown(testutil.Timeout()) + require.NoError(t, err) require.Len(t, exporter.GetSpans(), spansCount) + require.Equal(t, uint64(spansCount), bb.FlushedCount()) } func TestWriteAllOnClose(t *testing.T) { @@ -34,20 +34,21 @@ func TestWriteAllOnClose(t *testing.T) { } exporter := &exporterStub{} - rawTracer := New( + bb := New( exporter, Config{ BatchSize: 10, Timeout: 5 * time.Second, }, ) - trs := klogga.NewFactory(rawTracer).NamedPkg() + trs := klogga.NewFactory(bb).NamedPkg() for _, sp := range sps { sp.FlushTo(trs) } - err := rawTracer.Shutdown(testutil.Timeout()) + err := bb.Shutdown(testutil.Timeout()) require.NoError(t, err) require.Len(t, exporter.GetSpans(), cap(sps)) + require.Equal(t, uint64(cap(sps)), bb.FlushedCount()) } func TestBatchPerTimer(t *testing.T) { diff --git a/batcher/delay_drop.go b/batcher/delay_drop.go index 0289283..8312542 100644 --- a/batcher/delay_drop.go +++ b/batcher/delay_drop.go @@ -2,7 +2,7 @@ package batcher import ( "context" - "go.kl/klogga" + "github.com/KasperskyLab/klogga" "go.uber.org/atomic" "time" ) diff --git a/batcher/exporter_stub.go b/batcher/exporter_stub.go index d72bfd6..0c0f29b 100644 --- a/batcher/exporter_stub.go +++ b/batcher/exporter_stub.go @@ -2,7 +2,7 @@ package batcher import ( "context" - "go.kl/klogga" + "github.com/KasperskyLab/klogga" "sync" ) diff --git a/constants/reserved.go b/constants/reserved.go new file mode 100644 index 0000000..3f0994e --- /dev/null +++ b/constants/reserved.go @@ -0,0 +1,7 @@ +package constants + +const ( + SpanID = "span_id" + TraceID = "trace_id" + ParentSpanID = "parent_id" +) diff --git a/constants/tags/readme.md b/constants/tags/readme.md index 58b4db7..ae8b8d9 100644 --- a/constants/tags/readme.md +++ b/constants/tags/readme.md @@ -1,2 +1,5 @@ -A set of default (example) generic tag names \ No newline at end of file +A set of default (example) generic tag names + +Tags should be used as low-cardinality indexed attributes of a span. +Tags attributes should primarily used to filter data. diff --git a/constants/tags/tags.go b/constants/tags/tags.go index a43236d..155bed1 100644 --- a/constants/tags/tags.go +++ b/constants/tags/tags.go @@ -7,7 +7,7 @@ const ( Login = "login" User = "user" Group = "group" - Ip = "ip" + IP = "ip" Host = "host" Method = "method" Path = "path" @@ -15,9 +15,9 @@ const ( PagerFrom = "page_from" PagerCount = "page_count" - HttpStatus = "http_status" + HTTPStatus = "http_status" - Url = "url" + URL = "url" Email = "email" Role = "role" ) diff --git a/constants/vals/readme.md b/constants/vals/readme.md index 263bb9e..df1c78a 100644 --- a/constants/vals/readme.md +++ b/constants/vals/readme.md @@ -1,2 +1,5 @@ -A set of default (example) generic vals names \ No newline at end of file +A set of default (example) generic vals names. + +Vals are any other attributes of a span, including heavier data like text or json objects. +Vals should not be treated as indexed by default, but some storages allow it just fine. diff --git a/constants/vals/vals.go b/constants/vals/vals.go index 718dfc5..9cd6427 100644 --- a/constants/vals/vals.go +++ b/constants/vals/vals.go @@ -3,9 +3,9 @@ package vals type Val string const ( - SpanId = "span_id" - ParentSpanId = "parent_id" - Count = "count" + // Count should be used as a number of results of a function, if results are countable + // i.e. slice, array, but not limited to them. + Count = "count" RequestBody = "request_body" ResponseBody = "response_body" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f6e8aa5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.5" +services: + postgresql: + container_name: klogga-timescale + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + image: timescale/timescaledb:latest-pg13 + ports: + - "5432:5432" diff --git a/examples/basic_app/main.go b/examples/basic_app/main.go new file mode 100644 index 0000000..d9ad44f --- /dev/null +++ b/examples/basic_app/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/exporters/golog" + "time" +) + +func main() { + // kreating a factory, with the simplest exporter + tf := klogga.NewFactory(golog.New(nil)) + + // kreating a tracer with a package name + trs := tf.NamedPkg() + + // starting a span + // for now, we'll ignore context + span, _ := klogga.Start(context.Background()) + // span will be written on func exit + defer trs.Finish(span) + + // tag - potentially indexed + span.Tag("app", "hello-world") + // value - for metrics, or bigger values + span.Val("meaning", 42) + // sleep a bit, to have us some non-zero duration + time.Sleep(154 * time.Millisecond) + +} diff --git a/examples/batcher_tree_test.go b/examples/batcher_tree_test.go index 72ba241..5cd483c 100644 --- a/examples/batcher_tree_test.go +++ b/examples/batcher_tree_test.go @@ -2,7 +2,7 @@ package examples import ( "context" - "go.kl/klogga" + "github.com/KasperskyLab/klogga" "testing" ) diff --git a/examples/component_creation_test.go b/examples/component_creation_test.go new file mode 100644 index 0000000..a82a57c --- /dev/null +++ b/examples/component_creation_test.go @@ -0,0 +1,49 @@ +package examples + +import ( + "context" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/exporters/golog" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + "strings" + "testing" +) + +type componentExample struct { + trs klogga.Tracer +} + +func NewComponentExample(tf klogga.TracerProvider) *componentExample { + return &componentExample{ + trs: tf.Named("example"), + } +} + +func (c componentExample) AddSuffix(ctx context.Context, input string) (string, error) { + span, ctx := klogga.Start(ctx) + defer c.trs.Finish(span) + span.Val("input", input) + if len(input) < 4 { + return "", span.Err(errors.New("too short!")) + } + res := input + "=lalala" + span.Val("res", res) + return res, nil +} + +func TestComponentLog(t *testing.T) { + exporter := golog.New(nil) + sb := strings.Builder{} + tf := klogga.NewFactory(exporter, klogga.NewWriterExporter(&sb)) + ee := NewComponentExample(tf) + suffix, err := ee.AddSuffix(context.Background(), "test_test") + require.NoError(t, err) + require.NotEmpty(t, suffix) + + res := sb.String() + require.Contains(t, res, "example") + require.Contains(t, res, "[examples.componentExample]") + require.Contains(t, res, "I example [examples.componentExample] AddSuffix()") + require.Contains(t, res, "test_test") +} diff --git a/examples/many_spans_postgres/main.go b/examples/many_spans_postgres/main.go new file mode 100644 index 0000000..442cf61 --- /dev/null +++ b/examples/many_spans_postgres/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "context" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/batcher" + "github.com/KasperskyLab/klogga/exporters/golog" + "github.com/KasperskyLab/klogga/exporters/postgres" + "github.com/KasperskyLab/klogga/exporters/postgres/pgconnector" + "go.uber.org/fx" + "net/http" + "os" + "time" + + _ "net/http/pprof" +) + +func main() { + fx.New(createApp()).Run() +} + +func createApp() fx.Option { + return fx.Options( + fx.Provide( + func() (klogga.TracerProvider, error) { + // this should be called explicitly, + // so klogga doesn't have to have its own init() method + klogga.InitHostname() + + conn := pgconnector.PgConnector{ConnectionString: os.Getenv("KLOGGA_PG_CONNECTION_STRING")} + + err := conn.CreateSchemaIfNotExists(context.Background(), postgres.DefaultSchema) + if err != nil { + return nil, err + } + pgBatcher := batcher.New(postgres.New(&postgres.Conf{}, &conn, nil), batcher.ConfigDefault()) + return klogga.NewFactory(pgBatcher, golog.New(nil)), nil + }, + NewRunner, + ), + fx.Invoke( + func(tf klogga.TracerProvider) { + trs := tf.NamedPkg() + klogga.StartLeaf(context.Background()). + Tag("addr", "localhost:8080").FlushTo(trs) + go func() { + err := http.ListenAndServe("localhost:8080", nil) + if err != nil { + klogga.StartLeaf(context.Background()).ErrSpan(err).FlushTo(trs) + } + }() + }, + func(r *Runner, lf fx.Lifecycle) { + lf.Append( + fx.Hook{ + OnStart: r.Start, + OnStop: r.Stop, + }, + ) + }, + ), + ) +} + +type Runner struct { + trs klogga.Tracer + stop chan struct{} +} + +func NewRunner(tf klogga.TracerProvider) *Runner { + return &Runner{ + trs: tf.NamedPkg(), + stop: make(chan struct{}), + } +} + +func (r *Runner) Start(ctx context.Context) error { + span := klogga.StartLeaf(ctx) + defer r.trs.Finish(span) + go func() { + for i := 0; ; i++ { + r.LogSomething(i) + select { + case <-r.stop: + return + case <-time.After(1 * time.Millisecond): + } + } + }() + return nil +} + +func (r *Runner) Stop(ctx context.Context) error { + span := klogga.StartLeaf(context.Background()) + defer r.trs.Finish(span) + select { + case r.stop <- struct{}{}: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func (r *Runner) LogSomething(iteration int) { + span := klogga.StartLeaf(context.Background(), klogga.WithName("run_func")) + defer r.trs.Finish(span) + span.Val("iteration", iteration) +} diff --git a/examples/max_postgres/main.go b/examples/max_postgres/main.go new file mode 100644 index 0000000..571acb2 --- /dev/null +++ b/examples/max_postgres/main.go @@ -0,0 +1,111 @@ +package main + +import ( + "context" + "github.com/KasperskyLab/klogga" + fxAdapter "github.com/KasperskyLab/klogga/adapters/fx" + "github.com/KasperskyLab/klogga/batcher" + "github.com/KasperskyLab/klogga/exporters/postgres" + "github.com/KasperskyLab/klogga/exporters/postgres/pgconnector" + "github.com/prometheus/client_golang/prometheus/promhttp" + "go.uber.org/fx" + "net/http" + "os" + "time" +) + +func main() { + fx.New(createApp()).Run() +} + +func createApp() fx.Option { + return fx.Options( + fx.Provide( + func() (klogga.TracerProvider, error) { + // this should be called explicitly, + // so klogga doesn't have to have its own init() method + klogga.InitHostname() + + conn := pgconnector.PgConnector{ConnectionString: os.Getenv("KLOGGA_PG_CONNECTION_STRING")} + err := conn.Start(context.Background()) + if err != nil { + return nil, err + } + err = conn.CreateSchemaIfNotExists(context.Background(), postgres.DefaultSchema) + if err != nil { + return nil, err + } + pgBatcher := batcher.New( + postgres.New(&postgres.Conf{UseTimescale: true}, &conn, nil), + batcher.Config{BatchSize: 1000, Timeout: 500 * time.Millisecond}, + ) + return klogga.NewFactory(pgBatcher), nil + }, + NewRunner, + ), + fx.Invoke( + func(tf klogga.TracerProvider) { + trs := tf.NamedPkg() + promAddr := ":2112" + klogga.StartLeaf(context.Background()). + Tag("addr", promAddr).FlushTo(trs) + go func() { + http.Handle("/metrics", promhttp.Handler()) + err := http.ListenAndServe(promAddr, nil) + if err != nil { + klogga.StartLeaf(context.Background()).ErrSpan(err).FlushTo(trs) + } + }() + }, + func(r *Runner, lf fx.Lifecycle) { + lf.Append(fxAdapter.ToHook(r)) + }, + ), + ) +} + +type Runner struct { + trs klogga.Tracer + stop chan struct{} +} + +func NewRunner(tf klogga.TracerProvider) *Runner { + return &Runner{ + trs: tf.NamedPkg(), + stop: make(chan struct{}), + } +} + +func (r *Runner) Start(ctx context.Context) error { + span := klogga.StartLeaf(ctx) + defer r.trs.Finish(span) + go func() { + for i := 0; ; i++ { + r.LogSomething(i) + select { + case <-r.stop: + return + //case <-time.After(1 * time.Millisecond): + default: + } + } + }() + return nil +} + +func (r *Runner) Stop(ctx context.Context) error { + span := klogga.StartLeaf(context.Background()) + defer r.trs.Finish(span) + select { + case r.stop <- struct{}{}: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func (r *Runner) LogSomething(iteration int) { + span := klogga.StartLeaf(context.Background(), klogga.WithName("run_func")) + defer r.trs.Finish(span) + span.Val("iteration", iteration) +} diff --git a/examples/tracers_tree.go b/examples/tracers_tree.go index cb3843c..42e0880 100644 --- a/examples/tracers_tree.go +++ b/examples/tracers_tree.go @@ -1,16 +1,16 @@ package examples import ( + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/batcher" + "github.com/KasperskyLab/klogga/exporters/golog" + "github.com/KasperskyLab/klogga/exporters/influxdb18" client "github.com/influxdata/influxdb1-client" - "go.kl/klogga" - "go.kl/klogga/batcher" - "go.kl/klogga/golog_exporter" - "go.kl/klogga/influx_exporter" ) func CreateTracerTree() klogga.Exporter { logTrs := golog.New(nil) - influxTrs := influx_exporter.New(&influx_exporter.Conf{}, &client.Client{}, &klogga.NilExporterTracer{}) + influxTrs := influxdb18.New(&influxdb18.Conf{}, &client.Client{}, &klogga.NilExporterTracer{}) _ = klogga.NewFactory(logTrs, batcher.New(influxTrs, batcher.ConfigDefault())) diff --git a/exporter.go b/exporter.go index 80e85d8..3b8c498 100644 --- a/exporter.go +++ b/exporter.go @@ -1,7 +1,9 @@ //go:generate mockgen -source=exporter.go -destination=exporter_mocks.go -package=klogga package klogga -import "context" +import ( + "context" +) // Exporter generic tracer interface, should not be used outside implementations // to be more generic accepts batches right away diff --git a/golog_exporter/go_log.go b/exporters/golog/go_log.go similarity index 95% rename from golog_exporter/go_log.go rename to exporters/golog/go_log.go index baa8925..7d6e916 100644 --- a/golog_exporter/go_log.go +++ b/exporters/golog/go_log.go @@ -2,7 +2,7 @@ package golog import ( "context" - "go.kl/klogga" + "github.com/KasperskyLab/klogga" "log" "os" ) diff --git a/golog_exporter/go_log_test.go b/exporters/golog/go_log_test.go similarity index 82% rename from golog_exporter/go_log_test.go rename to exporters/golog/go_log_test.go index 233668e..0b52b6b 100644 --- a/golog_exporter/go_log_test.go +++ b/exporters/golog/go_log_test.go @@ -3,9 +3,9 @@ package golog import ( "context" "errors" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/util/testutil" "github.com/stretchr/testify/require" - "go.kl/klogga" - "go.kl/klogga/util/testutil" "testing" ) diff --git a/influx_exporter/influx_exporter.go b/exporters/influxdb18/influx_exporter.go similarity index 84% rename from influx_exporter/influx_exporter.go rename to exporters/influxdb18/influx_exporter.go index 953415c..4ebc2d8 100644 --- a/influx_exporter/influx_exporter.go +++ b/exporters/influxdb18/influx_exporter.go @@ -1,19 +1,19 @@ //go:generate mockgen -source=influx_exporter.go -destination=influx_exporter_mocks.go -package=influx_exporter -package influx_exporter +package influxdb18 import ( "context" "fmt" - "github.com/influxdata/influxdb1-client" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/constants" + influxClient "github.com/influxdata/influxdb1-client" "github.com/pkg/errors" - "go.kl/klogga" - "go.kl/klogga/constants/vals" "math" "strings" ) -type Tracer struct { +type Exporter struct { client InfluxClient errTrs klogga.Tracer conf *Conf @@ -38,23 +38,23 @@ func (c *Conf) PrecisionOrDefault() string { // InfluxClient separates tracer from influx implementation, in case something would be needed in between // (although it still depends on specific influx client types) type InfluxClient interface { - Write(bp client.BatchPoints) (*client.Response, error) + Write(bp influxClient.BatchPoints) (*influxClient.Response, error) } // New not an actual tracer, should be used with Batcher // conf - configures tracer, use empty struct by default // client - influx client implementation, no v2 support (yet) // errTrs - tracer, where influx errors will be written. Be careful about possible recursion! -func New(conf *Conf, client InfluxClient, errTrs klogga.Tracer) *Tracer { - return &Tracer{ +func New(conf *Conf, client InfluxClient, errTrs klogga.Tracer) *Exporter { + return &Exporter{ client: client, conf: conf, errTrs: errTrs, } } -func (t *Tracer) Write(ctx context.Context, spans []*klogga.Span) error { - points := make([]client.Point, 0, len(spans)) +func (t *Exporter) Write(_ context.Context, spans []*klogga.Span) error { + points := make([]influxClient.Point, 0, len(spans)) for _, span := range spans { span.Stop() @@ -69,7 +69,8 @@ func (t *Tracer) Write(ctx context.Context, spans []*klogga.Span) error { for key, val := range span.Vals() { fields[key] = AdjustValType(val) } - fields[vals.SpanId] = span.ID().String() + fields[constants.SpanID] = span.ID().String() + fields[constants.TraceID] = span.TraceID().String() errFlag := "" if span.Errs() != nil { @@ -95,7 +96,7 @@ func (t *Tracer) Write(ctx context.Context, spans []*klogga.Span) error { tags["dur"] = klogga.RoundDur(dur) points = append( - points, client.Point{ + points, influxClient.Point{ Measurement: t.measurement(span), Tags: tags, Time: span.StartedTs(), @@ -105,7 +106,7 @@ func (t *Tracer) Write(ctx context.Context, spans []*klogga.Span) error { ) } - bp := client.BatchPoints{ + bp := influxClient.BatchPoints{ Points: points, Database: t.conf.Database, Precision: t.conf.PrecisionOrDefault(), @@ -121,7 +122,7 @@ func (t *Tracer) Write(ctx context.Context, spans []*klogga.Span) error { return nil } -func (t *Tracer) measurement(span *klogga.Span) string { +func (t *Exporter) measurement(span *klogga.Span) string { measurement := strings.ToLower(span.Component().String()) if measurement == "" { measurement = span.Package() @@ -135,7 +136,7 @@ func (t *Tracer) measurement(span *klogga.Span) string { return measurement } -func (t *Tracer) Shutdown(context.Context) error { +func (t *Exporter) Shutdown(context.Context) error { return nil } diff --git a/influx_exporter/influx_exporter_mocks.go b/exporters/influxdb18/influx_exporter_mocks.go similarity index 98% rename from influx_exporter/influx_exporter_mocks.go rename to exporters/influxdb18/influx_exporter_mocks.go index 325d05e..44e077c 100644 --- a/influx_exporter/influx_exporter_mocks.go +++ b/exporters/influxdb18/influx_exporter_mocks.go @@ -2,7 +2,7 @@ // Source: influx_exporter.go // Package influx_exporter is a generated GoMock package. -package influx_exporter +package influxdb18 import ( reflect "reflect" diff --git a/influx_exporter/influx_exporter_test.go b/exporters/influxdb18/influx_exporter_test.go similarity index 93% rename from influx_exporter/influx_exporter_test.go rename to exporters/influxdb18/influx_exporter_test.go index 593ec87..c7521c5 100644 --- a/influx_exporter/influx_exporter_test.go +++ b/exporters/influxdb18/influx_exporter_test.go @@ -1,13 +1,13 @@ -package influx_exporter +package influxdb18 import ( "context" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/util/testutil" "github.com/golang/mock/gomock" client "github.com/influxdata/influxdb1-client" "github.com/pkg/errors" "github.com/stretchr/testify/require" - "go.kl/klogga" - "go.kl/klogga/util/testutil" "math" "testing" ) diff --git a/exporters/influxdb18/readme.md b/exporters/influxdb18/readme.md new file mode 100644 index 0000000..825d004 --- /dev/null +++ b/exporters/influxdb18/readme.md @@ -0,0 +1 @@ +Exporter implementation fot influxDB 1.8.* \ No newline at end of file diff --git a/exporters/otel/id_importer.go b/exporters/otel/id_importer.go new file mode 100644 index 0000000..962bcb3 --- /dev/null +++ b/exporters/otel/id_importer.go @@ -0,0 +1,57 @@ +package otel + +import ( + "context" + "github.com/KasperskyLab/klogga" + trace_sdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" +) + +func NewTracerProvider(opts ...trace_sdk.TracerProviderOption) *trace_sdk.TracerProvider { + opts = append(opts, trace_sdk.WithIDGenerator(IDImporter{})) + return trace_sdk.NewTracerProvider(opts...) +} + +// IDImporter allows OpenTelemetry spans to have same ids as klogga spans from which they were creates +type IDImporter struct { +} + +type idImporterKey struct{} + +type ids struct { + spanID klogga.SpanID + traceID klogga.TraceID +} + +func withIds(ctx context.Context, spanID klogga.SpanID, traceID klogga.TraceID) context.Context { + return context.WithValue( + ctx, idImporterKey{}, &ids{ + spanID: spanID, + traceID: traceID, + }, + ) +} + +func getIds(ctx context.Context) (bool, klogga.TraceID, klogga.SpanID) { + idsVal, ok := ctx.Value(idImporterKey{}).(*ids) + if !ok { + return false, klogga.TraceID{}, klogga.SpanID{} + } + return true, idsVal.traceID, idsVal.spanID +} + +func (i IDImporter) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) { + ok, traceID, spanID := getIds(ctx) + if !ok { + return trace.TraceID(klogga.NewTraceID()), trace.SpanID(klogga.NewSpanID()) + } + return trace.TraceID(traceID), trace.SpanID(spanID) +} + +func (i IDImporter) NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID { + ok, _, spanID := getIds(ctx) + if !ok { + return trace.SpanID(klogga.NewSpanID()) + } + return trace.SpanID(spanID) +} diff --git a/exporters/otel/otel_exporter.go b/exporters/otel/otel_exporter.go new file mode 100644 index 0000000..6c6b933 --- /dev/null +++ b/exporters/otel/otel_exporter.go @@ -0,0 +1,79 @@ +package otel + +import ( + "context" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/util/errs" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + "time" +) + +// Exporter basic exporter of klogga spans to otel tracer +type Exporter struct { + tracer trace.Tracer +} + +func New(tracer trace.Tracer) *Exporter { + return &Exporter{tracer: tracer} +} + +func (t *Exporter) Write(ctx context.Context, spans []*klogga.Span) error { + var spanErrs error + for _, span := range spans { + ts, err := trace.TraceState{}.Insert("klogga", span.ID().String()) + if err != nil { + spanErrs = errs.Append(spanErrs, err) + continue + } + ctx = withIds(ctx, span.ID(), span.TraceID()) + config := trace.SpanContextConfig{ + TraceState: ts, + TraceFlags: trace.FlagsSampled, + } + if pID := span.ParentID(); !pID.IsZero() { + config.SpanID = trace.SpanID(pID) + config.TraceID = trace.TraceID(span.TraceID()) + } + _, otelSpan := t.tracer.Start( + trace.ContextWithSpanContext(ctx, trace.NewSpanContext(config)), + span.Name(), + trace.WithTimestamp(span.StartedTs()), + ) + + for k, v := range span.Vals() { + otelSpan.SetAttributes( + attribute.KeyValue{ + Key: attribute.Key(k), + Value: ConvertValue(v), + }, + ) + } + for k, v := range span.Tags() { + otelSpan.SetAttributes( + attribute.KeyValue{ + Key: attribute.Key(k), + Value: ConvertValue(v), + }, + ) + } + if span.HasWarn() { + otelSpan.SetAttributes(attribute.String("warn", span.Warns().Error())) + } + if span.HasErr() { + otelSpan.RecordError(span.Errs()) + otelSpan.SetStatus(codes.Error, "E") + } + + // possibly should have something like this: + // otelSpan.SetAttributes(attribute.String("klogga_id", span.ID().String())) + time.Sleep(500 * time.Millisecond) + otelSpan.End() + } + return nil +} + +func (t *Exporter) Shutdown(context.Context) error { + return nil +} diff --git a/exporters/otel/otel_test.go b/exporters/otel/otel_test.go new file mode 100644 index 0000000..92d1212 --- /dev/null +++ b/exporters/otel/otel_test.go @@ -0,0 +1,104 @@ +package otel + +import ( + "context" + "fmt" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/constants/vals" + "github.com/KasperskyLab/klogga/util/testutil" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/sdk/trace" + _ "go.opentelemetry.io/otel/trace" + "strings" + "testing" + "time" +) + +func TestOtelExporter(t *testing.T) { + sb := strings.Builder{} + exporter, err := stdouttrace.New(stdouttrace.WithWriter(&sb), stdouttrace.WithPrettyPrint()) + require.NoError(t, err) + tp := NewTracerProvider(trace.WithSyncer(exporter)) + defer func() { require.NoError(t, tp.Shutdown(testutil.Timeout())) }() + + otelTrs := tp.Tracer("test123") + + trs := klogga.NewFactory(New(otelTrs)).NamedPkg() + span := klogga.StartLeaf(testutil.Timeout()) + span.Tag("key1", "test_key_value") + value := 741275 + span.Val(vals.Count, value) + span.Warn(errors.New("some_test_warn")) + time.Sleep(100 * time.Millisecond) + trs.Finish(span) + + t.Log(span.Stringify()) + + otelSpanStr := sb.String() + t.Logf(otelSpanStr) + require.Contains(t, otelSpanStr, "test_key_value") + require.Contains(t, otelSpanStr, fmt.Sprintf("%v", value)) + require.Contains(t, otelSpanStr, "some_test_warn") + require.Contains(t, otelSpanStr, span.ID().String()) + require.Contains(t, otelSpanStr, fmt.Sprintf("%x", span.ID().Bytes())) + require.Contains(t, otelSpanStr, fmt.Sprintf("%x", span.TraceID().Bytes())) +} + +func TestOtelExporterWithErr(t *testing.T) { + sb := strings.Builder{} + exporter, err := stdouttrace.New(stdouttrace.WithWriter(&sb), stdouttrace.WithPrettyPrint()) + require.NoError(t, err) + tp := trace.NewTracerProvider(trace.WithSyncer(exporter)) + defer func() { + require.NoError(t, tp.Shutdown(testutil.Timeout())) + }() + + trs := klogga.NewFactory(New(tp.Tracer("test123"))).NamedPkg() + span := klogga.StartLeaf(testutil.Timeout()) + span.Tag("key1", "test_key_value") + span.ErrVoid(errors.New("some_test_error")) + time.Sleep(100 * time.Millisecond) + trs.Finish(span) + t.Log(span.Stringify()) + + otelSpanStr := sb.String() + require.Contains(t, otelSpanStr, "test_key_value") + require.Contains(t, otelSpanStr, "some_test_error") + t.Logf(otelSpanStr) +} + +func TestOtelExporterWithParentSpan(t *testing.T) { + sb := strings.Builder{} + exporter, err := stdouttrace.New(stdouttrace.WithWriter(&sb), stdouttrace.WithPrettyPrint()) + require.NoError(t, err) + tp := NewTracerProvider(trace.WithSyncer(exporter)) + defer func() { require.NoError(t, tp.Shutdown(testutil.Timeout())) }() + otelTrs := tp.Tracer("test123") + + trs := klogga.NewFactory(New(otelTrs)).NamedPkg() + parentSpan, ctx := klogga.Start(testutil.Timeout()) + + span := klogga.StartLeaf(ctx) + span.Tag("key1", "test_key_value") + trs.Finish(span) + + t.Log(span.Stringify()) + + otelSpanStr := sb.String() + t.Logf(otelSpanStr) + require.Contains(t, otelSpanStr, "test_key_value") + require.Contains(t, otelSpanStr, fmt.Sprintf("%x", parentSpan.ID().Bytes())) + require.Contains(t, otelSpanStr, fmt.Sprintf("%x", span.ID().Bytes())) + require.Contains(t, otelSpanStr, fmt.Sprintf("%x", span.TraceID().Bytes())) +} + +func TestIdInContext(t *testing.T) { + spanID := klogga.NewSpanID() + ctx := context.Background() + ctx = withIds(ctx, spanID, klogga.NewTraceID()) + ok, _, extractedID := getIds(ctx) + require.True(t, ok) + require.Equal(t, spanID, extractedID) +} diff --git a/otel_exporter/values_mapper.go b/exporters/otel/values_mapper.go similarity index 87% rename from otel_exporter/values_mapper.go rename to exporters/otel/values_mapper.go index 94de6c7..267cf69 100644 --- a/otel_exporter/values_mapper.go +++ b/exporters/otel/values_mapper.go @@ -1,8 +1,8 @@ -package otel_exporter +package otel import ( "fmt" - "go.kl/klogga" + "github.com/KasperskyLab/klogga" "go.opentelemetry.io/otel/attribute" ) @@ -33,6 +33,6 @@ func ConvertValue(val interface{}) attribute.Value { case fmt.Stringer: return attribute.StringValue(typed.String()) default: - return attribute.StringValue(klogga.NewObjectVal(typed).String()) + return attribute.StringValue(klogga.ValObject(typed).String()) } } diff --git a/exporters/postgres/connection.go b/exporters/postgres/connection.go new file mode 100644 index 0000000..c60d8dd --- /dev/null +++ b/exporters/postgres/connection.go @@ -0,0 +1,21 @@ +package postgres + +import ( + "context" + "database/sql" +) + +type Connection interface { + ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) + QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) + BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) + // Close is called when the exporter no longer needs the connection + Close() error +} + +// Connector provides PG connections in an abstract way +type Connector interface { + GetConnection(ctx context.Context) (Connection, error) + // Stop for cleanup, will be called when tracer closes + Stop(ctx context.Context) error +} diff --git a/pg_exporter/pg_exporter.go b/exporters/postgres/pg_exporter.go similarity index 55% rename from pg_exporter/pg_exporter.go rename to exporters/postgres/pg_exporter.go index f49810e..2e3c405 100644 --- a/pg_exporter/pg_exporter.go +++ b/exporters/postgres/pg_exporter.go @@ -1,16 +1,17 @@ -package pg_exporter +package postgres import ( "context" - "database/sql" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/constants" + "github.com/KasperskyLab/klogga/constants/vals" + "github.com/KasperskyLab/klogga/exporters/golog" + "github.com/KasperskyLab/klogga/util/errs" + "github.com/KasperskyLab/klogga/util/reflectutil" + "github.com/KasperskyLab/klogga/util/stringutil" "github.com/Masterminds/squirrel" "github.com/lib/pq" "github.com/pkg/errors" - "go.kl/klogga" - "go.kl/klogga/constants/vals" - "go.kl/klogga/golog_exporter" - "go.kl/klogga/util/errs" - "go.kl/klogga/util/reflectutil" "strings" "sync" "time" @@ -19,10 +20,11 @@ import ( //go:generate mockgen -source=pg_exporter.go -destination=pg_exporter_mock.go -package=pg_exporter const ( - defaultSchema = "audit" - errorMetricsTableName = "error_metrics" - defaultWriteTimeout = time.Second + ErrorPostgresTableName = "error_postgres" + DefaultSchema = "audit" + ErrorPostgresTable = DefaultSchema + "." + ErrorPostgresTableName + defaultWriteTimeout = time.Second msgUnableToConnectPG = "unable to connect PG" msgUnableToBeginTx = "unable to begin TX" msgUnableToCommitTx = "unable to commit TX" @@ -33,23 +35,20 @@ const ( msgUnableToExec = "unable to exec" ) +// Conf parameters on how klogga works with DB +// not the connection string etc. type Conf struct { - SchemaName string + SchemaName string + // applied per-table, and only for writing itself, without updating the schema WriteTimeout time.Duration - UseTimescale bool -} -type Connection interface { - ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) - QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) - BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) -} + // don't create any DB structure automatically + // requires all the tables to be created beforehand, with exactly required fields + SkipSchemaCreation bool -// Connector provides PG connections in an abstract way -type Connector interface { - GetConnection(ctx context.Context) (Connection, error) - // Close for cleanup, will be called when tracer closes - Close(ctx context.Context) error + LoadSchemaTimeout time.Duration + // automatically created tables are declared as timescale hypertables + UseTimescale bool } // Exporter writes Spans to postgres @@ -58,41 +57,50 @@ type Exporter struct { cfg *Conf connFactory Connector psql squirrel.StatementBuilderType - errorTracer klogga.Tracer + trs klogga.Tracer + timeCol *ColumnSchema sysCols *TableSchema errTable *TableSchema - tables map[string]*TableSchema - loadSchema sync.Once + tables map[string]*TableSchema + tablesLock *sync.Mutex + loadSchemaOnce *sync.Once } // New to be used with batcher // cfg - config // connFactory - connection to PG -// errorTracer - an alternative tracer to write its own errors to -func New(cfg *Conf, connFactory Connector, errorTracer klogga.Tracer) *Exporter { - if errorTracer == nil { - errorTracer = klogga.NewFactory(golog.New(nil)).NamedPkg() - klogga.StartLeaf(context.Background()).Warn(errors.New("using default golog tracer for pg errors")). - FlushTo(errorTracer) +// trs - a tracer to write pg_exporter logs, like DB changes, pass nil to setup default golog tracer +func New(cfg *Conf, connFactory Connector, trs klogga.Tracer) *Exporter { + if trs == nil { + trs = klogga.NewFactory(golog.New(nil)).NamedPkg() + klogga.StartLeaf(context.Background()). + ErrSpan(errors.New("using default golog tracer for pg errors")). + FlushTo(trs) } if cfg.SchemaName == "" { - cfg.SchemaName = defaultSchema + cfg.SchemaName = DefaultSchema } if cfg.WriteTimeout <= 0 { cfg.WriteTimeout = defaultWriteTimeout } + if cfg.LoadSchemaTimeout <= 0 { + cfg.LoadSchemaTimeout = defaultWriteTimeout * 10 + } + // base set of columns for each table + timeCol := &ColumnSchema{"time", "timestamp without time zone", "", false} sysCols := NewTableSchema( []*ColumnSchema{ - {"time", "timestamp without time zone", "default timezone('UTC'::text, statement_timestamp())", false}, - {"id", "uuid", "", false}, + timeCol, + {"id", "bytea", "", false}, + {constants.TraceID, "uuid", "", false}, {"host", "text", "", false}, {"pkg_class", "text", "", false}, {"name", "text", "", false}, - {"parent", "uuid", "", true}, + {"parent", "bytea", "", true}, {"error", "text", "", true}, {"warn", "text", "", true}, {"duration", "bigint", "", false}, @@ -107,77 +115,119 @@ func New(cfg *Conf, connFactory Connector, errorTracer klogga.Tracer) *Exporter ) return &Exporter{ - cfg: cfg, - connFactory: connFactory, - psql: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar), - errorTracer: errorTracer, - sysCols: sysCols, - errTable: errTable, - tables: make(map[string]*TableSchema), - loadSchema: sync.Once{}, + cfg: cfg, + connFactory: connFactory, + psql: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar), + trs: trs, + timeCol: timeCol, + sysCols: sysCols, + errTable: errTable, + tables: make(map[string]*TableSchema), + tablesLock: &sync.Mutex{}, + loadSchemaOnce: &sync.Once{}, } } +// Start for compatibility with Start / Stop interface +func (e *Exporter) Start(context.Context) error { + return nil +} + +// Stop for compatibility with Start / Stop interface func (e *Exporter) Stop(ctx context.Context) error { return e.Shutdown(ctx) } +func (e *Exporter) ConnFactory() Connector { + return e.connFactory +} + func (e *Exporter) Shutdown(ctx context.Context) error { - pgErr := e.connFactory.Close(ctx) - return pgErr + span, ctx := klogga.Start(ctx) + defer e.trs.Finish(span) + pgErr := e.connFactory.Stop(ctx) + return span.Err(pgErr) } func (e *Exporter) Write(ctx context.Context, spans []*klogga.Span) error { - ctx, cancel := context.WithTimeout(ctx, e.cfg.WriteTimeout) - defer cancel() span, ctx := klogga.Start(ctx) - defer e.finishLogSpan(span) + defer e.writeIfErr(span) if len(spans) == 0 { return nil } // On first write cache schema - // TODO maybe reload the schema periodically - e.loadSchema.Do( + e.loadSchemaOnce.Do( func() { - if err := e.loadSchemas(ctx); err != nil { - e.loadSchema = sync.Once{} + err := e.loadSchemas(ctx) + if err != nil { + e.loadSchemaOnce = &sync.Once{} } }, ) - datasets, errSpans := e.CreateRecordSets(spans...) + recordSets, errSpans := e.createRecordSets(spans...) e.writeErrSpans(ctx, errSpans) - for tableName, dataset := range datasets { - schema, found := e.tables[tableName] - if !found { - if err := e.createTable(ctx, tableName, e.sysCols.Merge(dataset.Schema.Columns())); err != nil { - span.ErrVoid(err) + for tableName, recordSet := range recordSets { + if !e.cfg.SkipSchemaCreation { + err := e.updateSchema(ctx, tableName, recordSet) + if span.Err(err) != nil { continue } - e.tables[tableName] = dataset.Schema - } else { - alterSchema, errSpans := schema.GetAlterSchema(dataset) - if len(errSpans) > 0 { - e.writeErrSpans(ctx, errSpans) - continue - } - if err := e.alterTable(ctx, tableName, alterSchema); err != nil { - span.ErrVoid(err) - continue - } - e.tables[tableName] = e.tables[tableName].Merge(alterSchema.Columns()) } - e.writeDataset(ctx, tableName, dataset) + e.writeRecordSet(ctx, tableName, recordSet) + } + if span.HasErr() { + // on any errors gotta trigger schema reload + e.loadSchemaOnce = &sync.Once{} } return nil } -func (e *Exporter) writeDataset(ctx context.Context, tableName string, dataset RecordSet) { +func (e *Exporter) updateSchema(ctx context.Context, tableName string, dataset RecordSet) error { + span, ctx := klogga.Start(ctx) + defer e.writeIfErr(span) + + span.Tag("table", tableName) + e.tablesLock.Lock() + defer e.tablesLock.Unlock() + schema, found := e.tables[tableName] + if !found { + err := e.createTable(ctx, tableName, e.sysCols.Merge(dataset.Schema.Columns())) + if err != nil { + return span.Err(err) + } + e.tables[tableName] = dataset.Schema + } else { + alterSchema, failures := schema.GetAlterSchema(dataset) + if len(failures) > 0 { + e.writeErrSpans(ctx, failures) + return span.Err( + errors.Errorf( + "alter table failed (%v failures), first failure: %v", len(failures), failures[0].Err(), + ), + ) + } + if alterSchema.IsZero() { + return nil + } + if err := e.alterTable(ctx, tableName, alterSchema); err != nil { + return span.Err(err) + } + e.tables[tableName] = e.tables[tableName].Merge(alterSchema.Columns()) + } + return nil +} + +func (e *Exporter) writeRecordSet(ctx context.Context, tableName string, recordSet RecordSet) { + ctx, cancel := context.WithTimeout(ctx, e.cfg.WriteTimeout) + defer cancel() span, ctx := klogga.Start(ctx) - defer e.finishLogSpan(span) + defer func() { + e.writeIfErr(span) + }() span.Tag("table", tableName) conn, err := e.connFactory.GetConnection(ctx) @@ -185,6 +235,7 @@ func (e *Exporter) writeDataset(ctx context.Context, tableName string, dataset R span.ErrVoid(errors.Wrap(err, msgUnableToConnectPG)) return } + defer func() { span.DeferErr(conn.Close()) }() isCommitted := false tx, err := conn.BeginTx(ctx, nil) @@ -198,9 +249,9 @@ func (e *Exporter) writeDataset(ctx context.Context, tableName string, dataset R } }() - query := pq.CopyInSchema(e.cfg.SchemaName, tableName, dataset.Schema.ColumnNames()...) + query := pq.CopyInSchema(e.cfg.SchemaName, tableName, recordSet.Schema.ColumnNames()...) span.Val(vals.Query, query) - span.Val("columns_count", dataset.Schema.ColumnsCount()) + span.Val("columns_count", recordSet.Schema.ColumnsCount()) stmt, err := tx.Prepare(query) if err != nil { span.ErrVoid(errors.Wrap(err, msgUnableToPrepareStatement)) @@ -210,7 +261,7 @@ func (e *Exporter) writeDataset(ctx context.Context, tableName string, dataset R span.DeferErr(errors.Wrap(stmt.Close(), "unable to close statement")) }() - for _, span := range dataset.Spans { + for _, span := range recordSet.Spans { strErr := "" if sErr := errs.Append(span.Errs(), span.DeferErrs()); sErr != nil { strErr = sErr.Error() @@ -222,17 +273,18 @@ func (e *Exporter) writeDataset(ctx context.Context, tableName string, dataset R vv := []interface{}{ span.StartedTs(), - span.ID().AsUUID(), + span.ID().Bytes(), + span.TraceID().AsUUID(), span.Host(), span.PackageClass(), span.Name(), - span.ParentID().AsNullableUUID(), + span.ParentID().AsNullableBytes(), strErr, strWarn, span.Duration(), } - for _, colName := range dataset.Schema.ColumnNames()[e.sysCols.ColumnsCount():] { + for _, colName := range recordSet.Schema.ColumnNames()[e.sysCols.ColumnsCount():] { val := findColumnValue(span, colName) if reflectutil.IsNil(val) { vv = append(vv, nil) @@ -248,9 +300,7 @@ func (e *Exporter) writeDataset(ctx context.Context, tableName string, dataset R } } - // Due to asynchronous nature of "Exec" need to call Exec(nil) to sync the COPY stream - // and to get any errors from pending data - if _, err = stmt.Exec(); err != nil { + if _, err = stmt.ExecContext(ctx); err != nil { span.ErrVoid(errors.Wrap(err, "unable to finish COPY statement")) return } @@ -263,19 +313,20 @@ func (e *Exporter) writeDataset(ctx context.Context, tableName string, dataset R func (e *Exporter) createTable(ctx context.Context, tableName string, schema *TableSchema) error { span, ctx := klogga.Start(ctx) - defer e.finishLogSpan(span) + defer e.trs.Finish(span) - q := schema.CreateTableStatement(e.cfg.SchemaName, tableName, e.cfg.UseTimescale) + q := schema.CreateTableStatement(e.cfg.SchemaName, tableName, e.timeCol, e.cfg.UseTimescale) span. Tag("table", tableName). Val("columns_count", schema.ColumnsCount()). - Val("columns", strings.Join(schema.ColumnNames(), ",")) + Val("columns", strings.Join(schema.ColumnNames(), ",")). + Val(vals.Query, q) conn, err := e.connFactory.GetConnection(ctx) if err != nil { return span.Err(errors.Wrap(err, msgUnableToConnectPG)) } - + defer func() { span.DeferErr(conn.Close()) }() if _, err := conn.ExecContext(ctx, q); err != nil { span.Val(vals.Query, q) return span.Err(errors.Wrap(err, msgUnableToExec)) @@ -285,8 +336,8 @@ func (e *Exporter) createTable(ctx context.Context, tableName string, schema *Ta func (e *Exporter) alterTable(ctx context.Context, tableName string, schema *TableSchema) error { span, ctx := klogga.Start(ctx) - defer e.finishLogSpan(span) - if schema.ColumnsCount() <= 0 { + defer e.trs.Finish(span) + if schema.IsZero() { return nil } @@ -300,22 +351,27 @@ func (e *Exporter) alterTable(ctx context.Context, tableName string, schema *Tab if err != nil { return span.Err(errors.Wrap(err, msgUnableToConnectPG)) } + defer func() { span.DeferErr(conn.Close()) }() if _, err := conn.ExecContext(ctx, q); err != nil { return span.Err(errors.Wrap(err, msgUnableToExec)) } return nil } +// loadSchemas loads schema to cache from postgres func (e *Exporter) loadSchemas(ctx context.Context) error { span, ctx := klogga.Start(ctx) - defer e.finishLogSpan(span) - + defer e.trs.Finish(span) + span.Val("timeout", e.cfg.LoadSchemaTimeout) + ctx, cancel := context.WithTimeout(ctx, e.cfg.LoadSchemaTimeout) + defer cancel() conn, err := e.connFactory.GetConnection(ctx) if err != nil { return span.Err(errors.Wrap(err, msgUnableToConnectPG)) } + defer func() { span.DeferErr(conn.Close()) }() - query, args := e.psql.Select("table_name", "column_name", "is_nullable", "data_type"). + query, args := e.psql.Select("table_name", "column_name", "data_type"). From("information_schema.columns"). Where(squirrel.Eq{"table_schema": e.cfg.SchemaName}). MustSql() @@ -333,27 +389,33 @@ func (e *Exporter) loadSchemas(ctx context.Context) error { span.DeferErr(rows.Close()) }() + tables := map[string]*TableSchema{} for rows.Next() { var tableName string - var columnName string - var isNullable string - var dataType string - - if err := rows.Scan(&tableName, &columnName, &isNullable, &dataType); err != nil { + var colSchema ColumnSchema + if err := rows.Scan(&tableName, &colSchema.Name, &colSchema.DataType); err != nil { return span.Err(errors.Wrap(err, magUnableToScan)) } - - if _, found := e.tables[tableName]; !found { - e.tables[tableName] = NewTableSchema([]*ColumnSchema{}) + if _, found := tables[tableName]; !found { + tables[tableName] = NewTableSchema([]*ColumnSchema{}) } - - colSchema := ColumnSchema{ - Name: columnName, - DataType: dataType, + tables[tableName].AddColumn(colSchema) + } + _, ok := tables[ErrorPostgresTableName] + if !ok { + err := e.createTable(ctx, ErrorPostgresTableName, e.errTable) + if err != nil { + span.ErrVoid(errors.Wrapf(err, "failed to create table for errors: %s", ErrorPostgresTable)) } - e.tables[tableName].AddColumn(colSchema) } - span.Val("table_count", len(e.tables)) + + names := make([]string, 0, len(tables)) + for name := range tables { + names = append(names, name) + } + span.Val("tables", strings.Join(names, ",")) + span.Val("count", len(tables)) + e.tables = tables return nil } @@ -362,27 +424,20 @@ func (e *Exporter) writeErrSpans(ctx context.Context, errDescrs []ErrDescriptor) return } span, ctx := klogga.Start(ctx) - defer e.finishLogSpan(span) + defer e.writeIfErr(span) span.Val(vals.Count, len(errDescrs)) - _, ok := e.tables[errorMetricsTableName] - if !ok { - err := e.createTable(ctx, errorMetricsTableName, e.errTable) - if err != nil { - span.ErrVoid(errors.Wrap(err, "failed to create table for errors")) - return - } - } - conn, err := e.connFactory.GetConnection(ctx) if err != nil { span.ErrVoid(errors.Wrap(err, msgUnableToConnectPG)) return } + defer func() { span.DeferErr(conn.Close()) }() - statementText := e.errTable.InsertStatement(e.cfg.SchemaName, errorMetricsTableName) + statementText := e.errTable.InsertStatement(e.cfg.SchemaName, ErrorPostgresTableName) - for _, errDescr := range errDescrs { + for i := 0; i < len(errDescrs); i++ { + errDescr := errDescrs[i] warn := "" if errDescr.Warn() != nil { warn = errDescr.Warn().Error() @@ -391,11 +446,12 @@ func (e *Exporter) writeErrSpans(ctx context.Context, errDescrs []ErrDescriptor) ctx, statementText, errDescr.Span.StartedTs(), - errDescr.Span.ID().AsUUID(), + errDescr.Span.ID().Bytes(), + errDescr.Span.TraceID().AsUUID(), errDescr.Span.Host(), errDescr.Span.PackageClass(), errDescr.Span.Name(), - errDescr.Span.ParentID().AsNullableUUID(), + errDescr.Span.ParentID().AsNullableBytes(), errDescr.Err().Error(), warn, errDescr.Span.Duration(), @@ -407,22 +463,16 @@ func (e *Exporter) writeErrSpans(ctx context.Context, errDescrs []ErrDescriptor) } } -func (e *Exporter) finishLogSpan(span *klogga.Span) { - if span.HasErr() || span.HasWarn() || span.HasDeferErr() { - e.errorTracer.Finish(span) - } -} - type RecordSet struct { Schema *TableSchema Spans []*klogga.Span } -// CreateRecordSets creates record sets from spans, grouped by table name +// createRecordSets creates record sets from spans, grouped by table name // if span column can't be placed in the structure, it is added to []ErrDescriptor // (this happens when span has a data type in tag/val that is different from the expected type // in the existing structure) -func (e *Exporter) CreateRecordSets(spans ...*klogga.Span) (map[string]RecordSet, []ErrDescriptor) { +func (e *Exporter) createRecordSets(spans ...*klogga.Span) (map[string]RecordSet, []ErrDescriptor) { datasets := make(map[string]RecordSet) errSpans := make([]ErrDescriptor, 0) @@ -449,11 +499,11 @@ func (e *Exporter) CreateRecordSets(spans ...*klogga.Span) (map[string]RecordSet continue } if !strings.EqualFold(newColumn.DataType, col.DataType) { - errSpans = append(errSpans, NewErrDescriptor(span, newColumn, *col)) + errSpans = append(errSpans, newErrDescriptor(tableName, span, newColumn, *col)) } } - if len(errSpans) <= 0 { + if len(errSpans) == 0 { dataset.Spans = append(dataset.Spans, span) } @@ -468,6 +518,7 @@ func spanTableName(span *klogga.Span) string { if table == "" { table = span.Package() } + table = stringutil.ToSnakeCase(table) return table } @@ -490,3 +541,9 @@ func (e *Exporter) getSpanVals(span *klogga.Span) map[string]interface{} { } return result } + +func (e *Exporter) writeIfErr(span *klogga.Span) { + if span.HasErr() { + span.FlushTo(e.trs) + } +} diff --git a/pg_exporter/pg_exporter_test.go b/exporters/postgres/pg_exporter_test.go similarity index 58% rename from pg_exporter/pg_exporter_test.go rename to exporters/postgres/pg_exporter_test.go index 4814e04..3301cbe 100644 --- a/pg_exporter/pg_exporter_test.go +++ b/exporters/postgres/pg_exporter_test.go @@ -1,10 +1,10 @@ -package pg_exporter +package postgres import ( "encoding/json" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/util/testutil" "github.com/stretchr/testify/require" - "go.kl/klogga" - "go.kl/klogga/util/testutil" "testing" ) @@ -19,7 +19,7 @@ func TestMultipleDatasets(t *testing.T) { span2.Tag("tag", "val") trs := New(&Conf{}, nil, klogga.NilExporterTracer{}) - datasets, _ := trs.CreateRecordSets(span1, span2) + datasets, _ := trs.createRecordSets(span1, span2) require.Len(t, datasets, 2) _, found := datasets["table1"] require.True(t, found) @@ -34,7 +34,7 @@ func TestDatasetSchema(t *testing.T) { s2.SetComponent("postgres_test") trs := New(&Conf{}, nil, klogga.NilExporterTracer{}) - datasets, _ := trs.CreateRecordSets(s1, s2) + datasets, _ := trs.createRecordSets(s1, s2) require.Len(t, datasets, 1) _, found := datasets["postgres_test"].Schema.Column("t1") require.True(t, found) @@ -43,10 +43,6 @@ func TestDatasetSchema(t *testing.T) { require.Len(t, datasets["postgres_test"].Spans, 2) } -//func TestDoSomething(t *testing.T) { -// require.True(t, true) -//} - func TestDatasetSchemaTypeChecks(t *testing.T) { s1 := klogga.StartLeaf(testutil.Timeout()).Tag("tag1", "v1") s2 := klogga.StartLeaf(testutil.Timeout()).Tag("tag1", 1) @@ -54,7 +50,7 @@ func TestDatasetSchemaTypeChecks(t *testing.T) { s2.SetComponent("pg_test") trs := New(&Conf{}, nil, klogga.NilExporterTracer{}) - datasets, errSpans := trs.CreateRecordSets(s1, s2) + datasets, errSpans := trs.createRecordSets(s1, s2) require.Len(t, errSpans, 1) require.Len(t, datasets, 1) require.Len(t, datasets["pg_test"].Spans, 1) @@ -68,9 +64,32 @@ func TestAddJsonVal(t *testing.T) { err := json.Unmarshal([]byte(value), &jj) require.NoError(t, err) span.ValAsObj("json_field", jj) - pg := New(&Conf{}, nil, klogga.NilExporterTracer{}) - datasets, errCols := pg.CreateRecordSets(span) + pg := New(&Conf{}, nil, nil) + datasets, errCols := pg.createRecordSets(span) + require.Empty(t, errCols) + require.Len(t, datasets, 1) + require.Equal(t, 11, datasets["pg_test"].Schema.ColumnsCount()) +} + +func TestPgValueJsonb(t *testing.T) { + span := klogga.StartLeaf(testutil.Timeout()) + span.SetComponent("pg_test") + value := `{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["en-US,en;q=0.9,ru;q=0.8"],"Connection":["keep-alive"],"Content-Length":["0"],"Cookie":["s_fid=145F81CE863075C0-0F188C1885167E1E; s_cc=true; _ga=GA1.1.778486296.1606136302; tipadmin-dev=2195f6f1e24d9a3bafa3aef0f55916be"],"Ik4nnm_yfy":["0AFNS9GstJWK4gYwsSr8wxKqafIGOeljdJ75pwd46QEKBWFkbWluEIT6t4UGGgFbIhRxlsLlosMkyyWktTKm5Zu+EZCVHw=="],"Origin":["http://localhost:1234"],"Referer":["http://localhost:1234/requests/submissions"],"Sec-Ch-Ua":["\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"90\", \"Google Chrome\";v=\"90\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-origin"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"]}` + span.ValAsJson("json_field", value) + pg := New(&Conf{}, nil, nil) + datasets, errCols := pg.createRecordSets(span) require.Empty(t, errCols) require.Len(t, datasets, 1) - require.Equal(t, 10, datasets["pg_test"].Schema.ColumnsCount()) + for _, set := range datasets { + column, ok := set.Schema.Column("json_field") + require.True(t, ok) + require.Equal(t, PgJsonbTypeName, column.DataType) + } +} + +func TestPgValueJsonEmpty(t *testing.T) { + val := klogga.ValJson("") + pgt, v := GetPgTypeVal(val) + require.Equal(t, PgJsonbTypeName, pgt) + require.Equal(t, "null", v) } diff --git a/exporters/postgres/pgconnector/pg.sql b/exporters/postgres/pgconnector/pg.sql new file mode 100644 index 0000000..a2c321f --- /dev/null +++ b/exporters/postgres/pgconnector/pg.sql @@ -0,0 +1,23 @@ +SELECT + --1-- + time_bucket('1 min', main.time) AS tt, + --2-- + COUNT(*), + COUNT(*) / 60 as speed +FROM audit.main +WHERE main.time >= '2021-02-01' +GROUP BY tt +ORDER BY tt; + +SELECT pg_size_pretty(pg_total_relation_size('"audit"."main"')); + + +select count(*) from audit.main + +select time from audit.main +limit 100 + +select id, trace_id from audit.component; + +select * +from audit.error_metrics; diff --git a/exporters/postgres/pgconnector/pg_example_test.go b/exporters/postgres/pgconnector/pg_example_test.go new file mode 100644 index 0000000..faf8e17 --- /dev/null +++ b/exporters/postgres/pgconnector/pg_example_test.go @@ -0,0 +1,61 @@ +package pgconnector + +import ( + "context" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/batcher" + "github.com/KasperskyLab/klogga/exporters/golog" + "github.com/KasperskyLab/klogga/exporters/postgres" + "github.com/KasperskyLab/klogga/util/testutil" + "github.com/stretchr/testify/require" + "testing" +) + +func TestConnectPg(t *testing.T) { + // SETUP (arrange) + pgConn := &PgConnector{ + ConnectionString: testutil.IntegrationEnv(t, "KLOGGA_PG_CONNECTION_STRING"), + } + rawConn, err := pgConn.GetConnectionRaw(testutil.Timeout()) + require.NoError(t, err) + // schema needs to be created manually, to avoid klogga messing up your existing structure + _, err = rawConn.Exec("create schema if not exists audit") + require.NoError(t, err) + + // postgres exporter needs a separate tracer, to write its own errors + // this tracer shod use some simpler exporter, like golog, anything closer than external database + // be careful not to recurse that ) + errTracer := klogga.NewTestErrTracker(t, klogga.NewFactory(golog.New(nil)).NamedPkg()) + pgExporter := postgres.New( + &postgres.Conf{}, + pgConn, + errTracer, + ) + + // for most exporters batching is recommended + pgBatcher := batcher.New(pgExporter, batcher.ConfigDefault()) + // factory + tf := klogga.NewFactory(pgBatcher) + + // tracer, finally!! + trs := tf.Named("example_test") + + // SPAN CREATION (act) + span, _ := klogga.Start(context.Background()) + span.Tag("pg", "postgres") + span.Val("connection_string", pgConn.ConnectionString) + + trs.Finish(span) + + err = pgBatcher.Shutdown(testutil.Timeout()) + require.NoError(t, err) + + // CHECK STUFF (assert) + row := psql.Select("pg", "connection_string").From("audit.example_test"). + RunWith(rawConn).QueryRow() + var col1, col2 string + err = row.Scan(&col1, &col2) + require.NoError(t, err) + require.Equal(t, "postgres", col1) + require.Equal(t, pgConn.ConnectionString, col2) +} diff --git a/exporters/postgres/pgconnector/pg_integration_test.go b/exporters/postgres/pgconnector/pg_integration_test.go new file mode 100644 index 0000000..3f90cc7 --- /dev/null +++ b/exporters/postgres/pgconnector/pg_integration_test.go @@ -0,0 +1,295 @@ +package pgconnector + +import ( + "encoding/json" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/constants/vals" + "github.com/KasperskyLab/klogga/exporters/postgres" + "github.com/KasperskyLab/klogga/util/testutil" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +func TestWriteJson(t *testing.T) { + pg, conn := PgConn(t) + conn.DropIfExists("audit.test_json") + + span := klogga.StartLeaf(testutil.Timeout()) + span.SetComponent("test_json") + value := `{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["en-US,en;q=0.9,ru;q=0.8"],"Connection":["keep-alive"],"Content-Length":["0"],"Cookie":["s_fid=145F81CE863075C0-0F188C1885167E1E; s_cc=true; _ga=GA1.1.778486296.1606136302; tipadmin-dev=2195f6f1e24d9a3bafa3aef0f55916be"],"Ik4nnm_yfy":["0AFNS9GstJWK4gYwsSr8wxKqafIGOeljdJ75pwd46QEKBWFkbWluEIT6t4UGGgFbIhRxlsLlosMkyyWktTKm5Zu+EZCVHw=="],"Origin":["http://localhost:1234"],"Referer":["http://localhost:1234/requests/submissions"],"Sec-Ch-Ua":["\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"90\", \"Google Chrome\";v=\"90\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-origin"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"]}` + jj := make(map[string]interface{}) + err := json.Unmarshal([]byte(value), &jj) + require.NoError(t, err) + span.ValAsObj("json_field", jj) + span.Val("bytes_field", []byte("danila")) + + err = pg.Write(testutil.Timeout(), []*klogga.Span{span}) + require.NoError(t, err) + err = pg.Shutdown(testutil.Timeout()) + require.NoError(t, err) + + rows, err := psql.Select("id", "parent", "pkg_class", "json_field", "bytes_field"). + From("audit.test_json"). + RunWith(conn). + Query() + require.NoError(t, err) + hasNext := rows.Next() + require.True(t, hasNext) + require.NoError(t, rows.Err()) + types, err := rows.ColumnTypes() + require.NoError(t, err) + require.Len(t, types, 5) + + require.Equal(t, "JSONB", types[3].DatabaseTypeName()) + require.Equal(t, "BYTEA", types[4].DatabaseTypeName()) +} + +func TestReloadSchema(t *testing.T) { + pg, conn := PgConn(t) + conn.DropIfExists("audit.component"). + Truncate(postgres.ErrorPostgresTable) + + span1, _ := klogga.Start(testutil.Timeout()) + span1.SetComponent("component") + span1.Tag("tag", "text_value_1") + + err := pg.Write(testutil.Timeout(), klogga.SpanSlice{span1}) + require.NoError(t, err) + + row := psql.Select("count(*)").From("audit.component"). + RunWith(conn).QueryRow() + require.Equal(t, 1, conn.ScanInt(row)) + + pgNew, _ := PgConn(t) + + span2, _ := klogga.Start(testutil.Timeout()) + span2.SetComponent("component") + span2.Tag("tag", "text_value_2") + + err = pgNew.Write(testutil.Timeout(), klogga.SpanSlice{span2}) + require.NoError(t, err) + + row = psql.Select("count(*)").From("audit.component"). + RunWith(conn).QueryRow() + require.Equal(t, 2, conn.ScanInt(row)) +} + +func TestReloadSchemaCaseMessedUp(t *testing.T) { + pg, conn := PgConn(t) + conn.DropIfExists("audit.my_component"). + Truncate(postgres.ErrorPostgresTable) + + span1, _ := klogga.Start(testutil.Timeout()) + span1.SetComponent("MyComponent") + span1.Tag("tag", "text_value_1") + + err := pg.Write(testutil.Timeout(), klogga.SpanSlice{span1}) + require.NoError(t, err) + + row := psql.Select("count(*)").From("audit.my_component"). + RunWith(conn).QueryRow() + require.Equal(t, 1, conn.ScanInt(row)) + + pgNew, _ := PgConn(t) + + span2, _ := klogga.Start(testutil.Timeout()) + span2.SetComponent("MyComponent") + span2.Tag("tag", "text_value_2") + + err = pgNew.Write(testutil.Timeout(), klogga.SpanSlice{span2}) + require.NoError(t, err) + + row = psql.Select("count(*)").From("audit.my_component"). + RunWith(conn).QueryRow() + require.Equal(t, 2, conn.ScanInt(row)) +} + +func TestWriteWithoutTags(t *testing.T) { + pg, conn := PgConn(t) + conn.DropIfExists("audit.test_tags") + + span := klogga.StartLeaf(testutil.Timeout()) + span.SetComponent("test_tags") + value := `{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["en-US,en;q=0.9,ru;q=0.8"],"Connection":["keep-alive"],"Content-Length":["0"],"Cookie":["s_fid=145F81CE863075C0-0F188C1885167E1E; s_cc=true; _ga=GA1.1.778486296.1606136302; tipadmin-dev=2195f6f1e24d9a3bafa3aef0f55916be"],"Ik4nnm_yfy":["0AFNS9GstJWK4gYwsSr8wxKqafIGOeljdJ75pwd46QEKBWFkbWluEIT6t4UGGgFbIhRxlsLlosMkyyWktTKm5Zu+EZCVHw=="],"Origin":["http://localhost:1234"],"Referer":["http://localhost:1234/requests/submissions"],"Sec-Ch-Ua":["\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"90\", \"Google Chrome\";v=\"90\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-origin"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"]}` + jj := make(map[string]interface{}) + err := json.Unmarshal([]byte(value), &jj) + require.NoError(t, err) + + err = pg.Write(testutil.Timeout(), []*klogga.Span{span}) + require.NoError(t, err) + err = pg.Shutdown(testutil.Timeout()) + require.NoError(t, err) + + rows, err := psql. + Select("duration"). + From("audit.test_tags"). + RunWith(conn). + Query() + require.NoError(t, err) + + hasNext := rows.Next() + require.True(t, hasNext) + require.NoError(t, rows.Err()) +} + +func TestWriteError(t *testing.T) { + pg, conn := PgConn(t) + conn.DropIfExists("audit.test_error") + + span := klogga.StartLeaf(testutil.Timeout()) + span.SetComponent("test_error") + span.ErrVoid(errors.New("error-test")) + + err := pg.Write(testutil.Timeout(), []*klogga.Span{span}) + require.NoError(t, err) + err = pg.Shutdown(testutil.Timeout()) + require.NoError(t, err) + + rows, err := psql. + Select("duration"). + From("audit.test_error"). + RunWith(conn). + Query() + require.NoError(t, err) + + hasNext := rows.Next() + require.True(t, hasNext) + require.NoError(t, rows.Err()) +} + +func TestIncrementalSpan(t *testing.T) { + pg, conn := PgConn(t) + conn.DropIfExists("audit.test_incremental") + + span := klogga.StartLeaf(testutil.Timeout()) + span.SetComponent("test_incremental") + + span.Tag("login", "danila") + + require.NoError(t, pg.Write(testutil.Timeout(), []*klogga.Span{span})) + + row := psql.Select("login").From("audit.test_incremental"). + RunWith(conn).QueryRow() + l := "" + require.NoError(t, row.Scan(&l)) + require.Equal(t, "danila", l) + + span = klogga.StartLeaf(testutil.Timeout()) + span.SetComponent("test_incremental") + span.Val(vals.Count, 22) + err := pg.Write(testutil.Timeout(), []*klogga.Span{span}) + require.NoError(t, err) + + rows, err := psql.Select("count").From("audit.test_incremental"). + RunWith(conn).Query() + require.NoError(t, err) + require.True(t, rows.Next()) + require.True(t, rows.Next()) + var c *int + require.NoError(t, rows.Scan(&c)) + require.Equal(t, 22, *c) +} + +func TestBulkInsertMultipleTables(t *testing.T) { + pg, conn := PgConn(t) + conn.DropIfExists("audit.component1").DropIfExists("audit.component2") + + spans := make([]*klogga.Span, 0) + for i := 0; i < 3; i++ { + span1, _ := klogga.Start(testutil.Timeout()) + span1.SetComponent("component1") + span1.Tag("x", "x") + spans = append(spans, span1) + + span2, _ := klogga.Start(testutil.Timeout()) + span2.SetComponent("component2") + span2.Tag("x", "x") + spans = append(spans, span2) + } + err := pg.Write(testutil.Timeout(), spans) + require.NoError(t, err) + + row := psql.Select("count(*)").From("audit.component1"). + RunWith(conn).QueryRow() + var res int + require.NoError(t, row.Scan(&res)) + require.Equal(t, 3, res) + + row = psql.Select("count(*)").From("audit.component2"). + RunWith(conn).QueryRow() + require.NoError(t, row.Scan(&res)) + require.Equal(t, 3, res) +} + +func TestWritePgTypes(t *testing.T) { + pg, conn := PgConn(t) + conn.DropIfExists("audit.component") + + type T struct{ Name string } + span, _ := klogga.Start(testutil.Timeout()) + span.SetComponent("component") + span. + Tag("int", 11). + Tag("int64", int64(11)). + Tag("bool", true). + Tag("bytes", []byte{0x01}). + Tag("float", 1.1). + Tag("string", "string"). + Tag("time_time", time.Now()). + Tag("dur", time.Second). + ValAsObj("struct", T{Name: "xxx"}) + + err := pg.Write(testutil.Timeout(), klogga.SpanSlice{span}) + require.NoError(t, err) + + row := psql.Select("dur").From("audit.component"). + RunWith(conn).QueryRow() + var dur time.Duration + require.NoError(t, row.Scan(&dur)) + require.Equal(t, 1*time.Second, dur) +} + +func TestWritePgReservedColumnName(t *testing.T) { + pg, conn := PgConn(t) + conn.DropIfExists("audit.component") + span, _ := klogga.Start(testutil.Timeout()) + span.SetComponent("component") + span.Tag("group", "group") + + err := pg.Write(testutil.Timeout(), klogga.SpanSlice{span}) + require.NoError(t, err) + + row := psql.Select("\"group\"").From("audit.component"). + RunWith(conn).QueryRow() + + res := conn.ScanString(row) + require.Equal(t, "group", res) + +} + +func TestIncompatibleSchemaInOneBatch(t *testing.T) { + pg, conn := PgConn(t) + conn.DropIfExists("audit.component"). + Truncate(postgres.ErrorPostgresTable) + + span1, _ := klogga.Start(testutil.Timeout()) + span1.SetComponent("component") + span1.Tag("tag", "val") + span2, _ := klogga.Start(testutil.Timeout()) + span2.SetComponent("component") + span2.Tag("tag", int64(11)) + + err := pg.Write(testutil.Timeout(), klogga.SpanSlice{span1, span2}) + require.NoError(t, err) + + row := psql.Select("count(*)").From("audit.component"). + RunWith(conn).QueryRow() + + require.Equal(t, 1, conn.ScanInt(row)) + + row = psql.Select("count(*)").From(postgres.ErrorPostgresTable). + RunWith(conn).QueryRow() + require.Equal(t, 1, conn.ScanInt(row)) +} diff --git a/exporters/postgres/pgconnector/pg_testfixture_test.go b/exporters/postgres/pgconnector/pg_testfixture_test.go new file mode 100644 index 0000000..509df6e --- /dev/null +++ b/exporters/postgres/pgconnector/pg_testfixture_test.go @@ -0,0 +1,58 @@ +package pgconnector + +import ( + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/exporters/golog" + "github.com/KasperskyLab/klogga/exporters/postgres" + "github.com/KasperskyLab/klogga/util/testutil" + "github.com/Masterminds/squirrel" + "github.com/stretchr/testify/require" + "testing" +) + +var psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar) + +type SQLTestEx struct { + *ManagedSql + t *testing.T +} + +func (sql SQLTestEx) DropIfExists(table string) SQLTestEx { + _, err := sql.Exec("DROP TABLE IF EXISTS " + table) + require.NoError(sql.t, err) + return sql +} + +func (sql SQLTestEx) Truncate(table string) SQLTestEx { + _, _ = sql.Exec("TRUNCATE " + table) + return sql +} + +func (sql SQLTestEx) ScanInt(row squirrel.RowScanner) (res int) { + require.NoError(sql.t, row.Scan(&res)) + return res +} + +func (sql SQLTestEx) ScanString(row squirrel.RowScanner) (res string) { + require.NoError(sql.t, row.Scan(&res)) + return res +} + +func PgConn(t *testing.T) (*postgres.Exporter, SQLTestEx) { + t.Helper() + if testing.Short() { + t.Skip("longer integration test") + } + pgConn := &PgConnector{ + ConnectionString: testutil.IntegrationEnv(t, "KLOGGA_PG_CONNECTION_STRING"), + } + require.NoError(t, pgConn.Start(testutil.Timeout())) + conn, err := pgConn.GetConnectionRaw(testutil.Timeout()) + require.NoError(t, err) + + return postgres.New( + &postgres.Conf{}, + pgConn, + klogga.NewTestErrTracker(t, klogga.NewFactory(golog.New(nil)).NamedPkg()), + ), SQLTestEx{conn, t} +} diff --git a/exporters/postgres/pgconnector/pgconnector.go b/exporters/postgres/pgconnector/pgconnector.go new file mode 100644 index 0000000..831ff70 --- /dev/null +++ b/exporters/postgres/pgconnector/pgconnector.go @@ -0,0 +1,85 @@ +package pgconnector + +import ( + "context" + "github.com/KasperskyLab/klogga/exporters/postgres" + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" + "sync" +) + +// PgConnector the simplest PG connector implementation with sqlx +// Start doesn't need to be explicitly called, but it is preferred to check connections +type PgConnector struct { + ConnectionString string + MaxOpenConnections int + MaxIdleConnections int + + connLock sync.Mutex + conn *ManagedSql +} + +// ManagedSql wrapper that prevents users from closing the connection +type ManagedSql struct { + *sqlx.DB +} + +func (m ManagedSql) Close() error { + return nil +} + +func (p *PgConnector) Start(ctx context.Context) error { + return p.tryInitConnection(ctx) +} + +func (p *PgConnector) tryInitConnection(ctx context.Context) error { + if p.conn != nil { + return nil + } + p.connLock.Lock() + defer p.connLock.Unlock() + if p.conn != nil { + return nil + } + conn, err := sqlx.ConnectContext(ctx, "postgres", p.ConnectionString) + if err != nil { + return errors.Wrapf(err, "pg connect failed: %s", p.ConnectionString) + } + conn.SetMaxOpenConns(p.MaxOpenConnections) + conn.SetMaxIdleConns(p.MaxIdleConnections) + p.conn = &ManagedSql{conn} + return nil +} + +func (p *PgConnector) GetConnection(ctx context.Context) (postgres.Connection, error) { + if err := p.tryInitConnection(ctx); err != nil { + return nil, err + } + return p.conn, nil +} + +func (p *PgConnector) Stop(_ context.Context) error { + p.connLock.Lock() + defer p.connLock.Unlock() + if p.conn == nil { + return nil + } + return p.conn.Close() +} + +func (p *PgConnector) GetConnectionRaw(ctx context.Context) (*ManagedSql, error) { + if err := p.tryInitConnection(ctx); err != nil { + return nil, err + } + return p.conn, nil +} + +// CreateSchemaIfNotExists shorthand to create schema, if you don't want to do in manually +func (p *PgConnector) CreateSchemaIfNotExists(ctx context.Context, schema string) error { + conn, err := p.GetConnectionRaw(ctx) + if err != nil { + return err + } + _, err = conn.Exec("create schema if not exists " + schema) + return err +} diff --git a/pg_exporter/postgres_types.go b/exporters/postgres/postgres_types.go similarity index 62% rename from pg_exporter/postgres_types.go rename to exporters/postgres/postgres_types.go index 8797529..62fed95 100644 --- a/pg_exporter/postgres_types.go +++ b/exporters/postgres/postgres_types.go @@ -1,17 +1,17 @@ -package pg_exporter +package postgres import ( "encoding/json" "fmt" + "github.com/KasperskyLab/klogga" "github.com/pkg/errors" - "go.kl/klogga" "reflect" "strings" "time" ) -const pgTextTypeName = "TEXT" -const pgJsonbTypeName = "JSONB" +const PgTextTypeName = "text" +const PgJsonbTypeName = "jsonb" // GetPgTypeVal converts go type to a compatible PG type // structs are automatically converted to jsonb @@ -26,7 +26,7 @@ func GetPgTypeVal(a interface{}) (string, interface{}) { case float32, float64: return "float8", v case string: - return pgTextTypeName, v + return PgTextTypeName, v case time.Time: return "timestamp without time zone", v.UTC() case time.Duration: @@ -34,22 +34,22 @@ func GetPgTypeVal(a interface{}) (string, interface{}) { case *klogga.ObjectVal: data, err := json.Marshal(v) if err != nil { - return pgTextTypeName, fmt.Sprintf("%v", v) + return PgTextTypeName, fmt.Sprintf("%v", v) } - return pgJsonbTypeName, string(data) + return PgJsonbTypeName, string(data) case error: - return pgTextTypeName, v.Error() + return PgTextTypeName, v.Error() case fmt.Stringer: - return pgTextTypeName, v.String() + return PgTextTypeName, v.String() default: if v != reflect.Struct { data, err := json.Marshal(v) if err != nil { - return pgTextTypeName, fmt.Sprintf("%v", v) + return PgTextTypeName, fmt.Sprintf("%v", v) } - return pgJsonbTypeName, data + return PgJsonbTypeName, data } - return pgTextTypeName, fmt.Sprintf("%v", v) + return PgTextTypeName, fmt.Sprintf("%v", v) } } @@ -68,30 +68,32 @@ func findColumnValue(span *klogga.Span, name string) interface{} { // ErrDescriptor describes a problematic span column, it's description will be written to error_metrics type ErrDescriptor struct { + Table string Span *klogga.Span Column ColumnSchema ExistingColumn ColumnSchema } -func NewErrDescriptor(span *klogga.Span, col, existingCol ColumnSchema) ErrDescriptor { +func newErrDescriptor(table string, span *klogga.Span, col, existingCol ColumnSchema) ErrDescriptor { return ErrDescriptor{ + Table: table, Span: span, Column: col, ExistingColumn: existingCol, } } -func (bs *ErrDescriptor) Err() error { +func (e *ErrDescriptor) Err() error { return errors.Errorf( - "bad span column: %v is %v; %v expected", - bs.Column.Name, bs.Column.DataType, bs.ExistingColumn.DataType, + "bad span column (%s): '%v' column type is %v; %v type is expected", + e.Table, e.Column.Name, e.Column.DataType, e.ExistingColumn.DataType, ) } -func (bs *ErrDescriptor) Warn() error { - return bs.Span.Warns() +func (e *ErrDescriptor) Warn() error { + return e.Span.Warns() } func toPgColumnName(name string) string { - return strings.ToLower(strings.Replace(name, "-", "_", -1)) + return strings.ToLower(strings.ReplaceAll(name, "-", "_")) } diff --git a/pg_exporter/readme.md b/exporters/postgres/readme.md similarity index 100% rename from pg_exporter/readme.md rename to exporters/postgres/readme.md diff --git a/pg_exporter/table_schema.go b/exporters/postgres/table_schema.go similarity index 79% rename from pg_exporter/table_schema.go rename to exporters/postgres/table_schema.go index 4509fcf..e42c5b3 100644 --- a/pg_exporter/table_schema.go +++ b/exporters/postgres/table_schema.go @@ -1,4 +1,4 @@ -package pg_exporter +package postgres import ( "fmt" @@ -38,7 +38,7 @@ func (t TableSchema) ColumnNames() []string { // returns spans that cannot be written just by adding columns func (t TableSchema) GetAlterSchema(dataset RecordSet) (*TableSchema, []ErrDescriptor) { alterSchema := NewTableSchema([]*ColumnSchema{}) - errSpans := make([]ErrDescriptor, 0) + errDescriptors := make([]ErrDescriptor, 0) for _, colSchema := range dataset.Schema.Columns() { existingColSchema, found := t.Column(colSchema.Name) @@ -56,12 +56,12 @@ func (t TableSchema) GetAlterSchema(dataset RecordSet) (*TableSchema, []ErrDescr val := findColumnValue(span, colSchema.Name) pgType, _ := GetPgTypeVal(val) if !strings.EqualFold(pgType, existingColSchema.DataType) { - errSpans = append(errSpans, NewErrDescriptor(span, *colSchema, *existingColSchema)) + errDescriptors = append(errDescriptors, newErrDescriptor("", span, *colSchema, *existingColSchema)) } } } } - return alterSchema, errSpans + return alterSchema, errDescriptors } func (t *TableSchema) Merge(newCols []*ColumnSchema) *TableSchema { @@ -89,20 +89,22 @@ func (t TableSchema) Columns() []*ColumnSchema { return t.columns } -func (t TableSchema) InsertStatement(schema string, table string) string { +// InsertStatement NOT injection safe +func (t TableSchema) InsertStatement(schema string, tableName string) string { paramsStr := "$1" for i := 2; i <= t.ColumnsCount(); i++ { paramsStr += fmt.Sprintf(",$%v", i) } return fmt.Sprintf( "INSERT INTO %v.%v (%s) VALUES (%s)", - schema, table, + schema, tableName, strings.Join(t.ColumnNames(), ","), paramsStr, ) } -func (t TableSchema) CreateTableStatement(schema, tableName string, useTimescale bool) string { +// CreateTableStatement NOT injection safe +func (t TableSchema) CreateTableStatement(schema, tableName string, timeCol *ColumnSchema, useTimescale bool) string { b := strings.Builder{} b.WriteString(fmt.Sprintf("CREATE TABLE %s.%s\n", schema, tableName)) @@ -112,23 +114,24 @@ func (t TableSchema) CreateTableStatement(schema, tableName string, useTimescale if i != 0 { b.WriteString(",\n") } - b.WriteString(fmt.Sprintf("%v %v", column.Name, column.DataType)) + b.WriteString(fmt.Sprintf("\"%v\" %v", column.Name, column.DataType)) } b.WriteString(");\n") if useTimescale { - b.WriteString(fmt.Sprintf("SELECT create_hypertable('%v.%v','time');\n", schema, tableName)) + b.WriteString(fmt.Sprintf("SELECT create_hypertable('%v.%v','%s');\n", schema, tableName, timeCol.Name)) } return b.String() } +// AlterTableStatement NOT injection safe func (t TableSchema) AlterTableStatement(schema, tableName string) string { b := strings.Builder{} b.WriteString(fmt.Sprintf("ALTER TABLE %v.%v\n", schema, tableName)) for i, column := range t.columns { - b.WriteString(fmt.Sprintf(" ADD COLUMN %v %v", column.Name, column.DataType)) + b.WriteString(fmt.Sprintf(" ADD COLUMN \"%v\" %v", column.Name, column.DataType)) if i == len(t.columns)-1 { b.WriteString(";\n") } else { @@ -138,6 +141,10 @@ func (t TableSchema) AlterTableStatement(schema, tableName string) string { return b.String() } +func (t *TableSchema) IsZero() bool { + return t.ColumnsCount() <= 0 +} + type ColumnSchema struct { Name string DataType string @@ -145,7 +152,7 @@ type ColumnSchema struct { IsNullable bool } -func (s ColumnSchema) Sql() string { +func (s ColumnSchema) SQL() string { nullableStr := "null" if !s.IsNullable { nullableStr = "not " + nullableStr diff --git a/exporters/spancollector/collector.go b/exporters/spancollector/collector.go new file mode 100644 index 0000000..954ac9e --- /dev/null +++ b/exporters/spancollector/collector.go @@ -0,0 +1,24 @@ +package spancollector + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +// SpanCollector just collects written spans to public array for inspection +// currently for tests +type SpanCollector struct { + Spans []*klogga.Span +} + +func (s *SpanCollector) Write(_ context.Context, spans []*klogga.Span) error { + for _, span := range spans { + span.Val("span_index", len(s.Spans)) + s.Spans = append(s.Spans, span) + } + return nil +} + +func (s *SpanCollector) Shutdown(context.Context) error { + return nil +} diff --git a/exporters/testlog/test_log_exporter.go b/exporters/testlog/test_log_exporter.go new file mode 100644 index 0000000..9e0a47c --- /dev/null +++ b/exporters/testlog/test_log_exporter.go @@ -0,0 +1,27 @@ +package testlog + +import ( + "context" + "github.com/KasperskyLab/klogga" + "testing" +) + +type Exporter struct { + t *testing.T +} + +func NewTestLogExporter(t *testing.T) *Exporter { + t.Helper() + return &Exporter{t: t} +} + +func (e Exporter) Write(ctx context.Context, spans []*klogga.Span) error { + for _, span := range spans { + e.t.Logf(span.Stringify()) + } + return nil +} + +func (e Exporter) Shutdown(context.Context) error { + return nil +} diff --git a/factory.go b/factory.go index 98088de..700a91a 100644 --- a/factory.go +++ b/factory.go @@ -2,8 +2,8 @@ package klogga import ( "context" - "go.kl/klogga/util/errs" - "go.kl/klogga/util/reflectutil" + "github.com/KasperskyLab/klogga/util/errs" + "github.com/KasperskyLab/klogga/util/reflectutil" ) // Factory combines different exporters @@ -13,38 +13,56 @@ type Factory struct { exporters ExportersSlice // any errors are sent here - errExporter Exporter + // TBD + // errExporter Exporter } -func NewFactory(exporters ...Exporter) *Factory { - return &Factory{exporters: exporters} +// TracerProvider use to allow components/adapters to have name overrides +// implemented by Factory +type TracerProvider interface { + NamedPkg() Tracer + Named(componentName ComponentName) Tracer } -// tracer has a fixed component it writes to -// component is reset for the span on the Finish call -type tracerImpl struct { - componentName ComponentName - trs Exporter +func NewFactory(exporters ...Exporter) *Factory { + return &Factory{exporters: exporters} } // Named creates a named tracer for specified component func (tf *Factory) Named(componentName ComponentName) Tracer { - return &tracerImpl{componentName: componentName, trs: tf.exporters} + return &tracerImpl{componentName: componentName, tf: tf} } // NamedPkg creates a named tracer with the name as package name, where this constructor is called func (tf *Factory) NamedPkg() Tracer { p, _, _ := reflectutil.GetPackageClassFunc() - return &tracerImpl{ - trs: tf.exporters, - componentName: ComponentName(p), - } + return tf.Named(ComponentName(p)) } func (tf *Factory) Shutdown(ctx context.Context) error { return tf.exporters.Shutdown(ctx) } +// AddExporter adds another exporter to the factory. +// All previously created tracers as well as new tracers will write to all exporters. +// Do not use for concurrently executing goroutines that write spans. +// Intended to be used in the sequential app initialization. +func (tf *Factory) AddExporter(exporter Exporter) *Factory { + tf.exporters = append(tf.exporters, exporter) + return tf +} + +func (tf *Factory) write(ctx context.Context, spans []*Span) error { + return tf.exporters.Write(ctx, spans) +} + +// tracer has a fixed component it writes to +// component is reset for the span on the Finish call +type tracerImpl struct { + componentName ComponentName + tf *Factory +} + func (t *tracerImpl) Name() ComponentName { return t.componentName } @@ -56,7 +74,10 @@ func (t *tracerImpl) Finish(span *Span) { span.component = ComponentName(span.packageName) } span.Stop() - _ = t.trs.Write(context.Background(), []*Span{span}) + + // tracer shouldn't handle write errors + // exporters should deal with them their own way + _ = t.tf.write(context.Background(), []*Span{span}) } type ComponentName string @@ -68,12 +89,12 @@ func (c ComponentName) String() string { type ExportersSlice []Exporter func (t ExportersSlice) Write(ctx context.Context, spans []*Span) error { - var combinedErrs error + var childErrs error for _, child := range t { err := child.Write(ctx, spans) - combinedErrs = errs.Append(combinedErrs, err) + childErrs = errs.Append(childErrs, err) } - return nil + return childErrs } func (t ExportersSlice) Shutdown(ctx context.Context) error { diff --git a/factory_test.go b/factory_test.go index e653420..bbf05d8 100644 --- a/factory_test.go +++ b/factory_test.go @@ -2,9 +2,10 @@ package klogga import ( "context" + "github.com/KasperskyLab/klogga/util/testutil" "github.com/golang/mock/gomock" + "github.com/pkg/errors" "github.com/stretchr/testify/require" - "go.kl/klogga/util/testutil" "testing" ) @@ -57,3 +58,13 @@ func TestSetComponent(t *testing.T) { mockTrs.EXPECT().Shutdown(gomock.Any()) span.FlushTo(trs) } + +func TestExportersSlice_Write(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + exporter := NewMockExporter(ctrl) + exporter.EXPECT().Write(gomock.Any(), gomock.Any()).Return(errors.New("failed to write")) + + err := ExportersSlice{exporter}.Write(testutil.Timeout(), SpanSlice{}) + require.Error(t, err) +} diff --git a/fx_adapter/fx_adapter.go b/fx_adapter/fx_adapter.go deleted file mode 100644 index 7985071..0000000 --- a/fx_adapter/fx_adapter.go +++ /dev/null @@ -1,81 +0,0 @@ -package fx_adapter - -import ( - "context" - "go.kl/klogga" - "go.uber.org/fx" - "go.uber.org/fx/fxevent" - "reflect" - "strings" -) - -const FxComponent klogga.ComponentName = "fx" - -type fxTracer struct { - trs klogga.Tracer -} - -func (t fxTracer) LogEvent(event fxevent.Event) { - span := klogga.StartLeaf(context.Background()) - defer span.FlushTo(t.trs) - span.Tag("event", reflect.TypeOf(event).Name()) - switch e := event.(type) { - case *fxevent.OnStartExecuting: - span.Tag("callee", e.FunctionName) - span.Tag("caller", e.CallerName) - case *fxevent.OnStartExecuted: - span.Tag("callee", e.FunctionName) - span.Tag("caller", e.CallerName) - if e.Err != nil { - span.ErrVoid(e.Err) - } else { - span.Message("runtime:" + e.Runtime.String()) - } - case *fxevent.OnStopExecuting: - span.Tag("callee", e.FunctionName) - span.Tag("caller", e.CallerName) - case *fxevent.OnStopExecuted: - span.Tag("callee", e.FunctionName) - span.Tag("caller", e.CallerName) - if e.Err != nil { - span.ErrVoid(e.Err) - } else { - span.Message("runtime:" + e.Runtime.String()) - } - case *fxevent.Supplied: - span.Message("supplied type:" + e.TypeName).ErrVoid(e.Err) - case *fxevent.Provided: - span.Message("output types:" + strings.Join(e.OutputTypeNames, ",")).ErrVoid(e.Err) - case *fxevent.Invoking: - span.Message("function: " + e.FunctionName) - case *fxevent.Invoked: - if e.Err != nil { - span.ErrSpan(e.Err). - Message("function:" + e.FunctionName + " stack:" + e.Trace) - } - case *fxevent.Stopping: - span.Message("signal " + strings.ToUpper(e.Signal.String())) - case *fxevent.Stopped: - span.ErrVoid(e.Err) - case *fxevent.RollingBack: - span.ErrVoid(e.StartErr) - case *fxevent.RolledBack: - span.ErrVoid(e.Err) - case *fxevent.Started: - span.ErrVoid(e.Err) - case *fxevent.LoggerInitialized: - span.Message("function:" + e.ConstructorName).ErrVoid(e.Err) - } -} - -// Module send fx logs to standard tracer -func Module(tf klogga.Factory) fx.Option { - fxTrs := &fxTracer{trs: tf.Named(FxComponent)} - return fx.Options( - fx.WithLogger( - func() (fxevent.Logger, error) { - return fxTrs, nil - }, - ), - ) -} diff --git a/go.mod b/go.mod index c662aa9..92e2c07 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,32 @@ -module go.kl/klogga +module github.com/KasperskyLab/klogga go 1.16 require ( - github.com/Masterminds/squirrel v1.5.0 + github.com/Masterminds/squirrel v1.5.2 github.com/golang/mock v1.6.0 - github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/google/uuid v1.3.0 github.com/hashicorp/go-multierror v1.1.1 github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab github.com/jmoiron/sqlx v1.3.4 - github.com/kr/text v0.2.0 // indirect - github.com/lib/pq v1.10.2 - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/lib/pq v1.10.4 github.com/pkg/errors v0.9.1 - github.com/satori/go.uuid v1.2.0 + github.com/prometheus/client_golang v1.12.1 github.com/stretchr/testify v1.7.0 - go.opentelemetry.io/otel v1.0.1 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.0.1 - go.opentelemetry.io/otel/sdk v1.0.1 - go.opentelemetry.io/otel/trace v1.0.1 + go.opentelemetry.io/otel v1.3.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.3.0 + go.opentelemetry.io/otel/sdk v1.3.0 + go.opentelemetry.io/otel/trace v1.3.0 go.uber.org/atomic v1.9.0 + go.uber.org/fx v1.16.0 +) + +require ( + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect go.uber.org/dig v1.13.0 // indirect - go.uber.org/fx v1.15.0 go.uber.org/multierr v1.7.0 // indirect - go.uber.org/zap v1.19.1 // indirect + go.uber.org/zap v1.20.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) diff --git a/go.sum b/go.sum index 6776a4b..daf390e 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,170 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8= -github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE= +github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -34,35 +175,77 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opentelemetry.io/otel v1.0.1 h1:4XKyXmfqJLOQ7feyV5DB6gsBFZ0ltB8vLtp6pj4JIcc= -go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.0.1 h1:QaXn87hD37gomnr0W9OVju7ouaijrT7+92uurmn2zvQ= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.0.1/go.mod h1:B1r9v/IqMtkB0lIGbbayqT6f2awSH0EDZya1Yu4p1pU= -go.opentelemetry.io/otel/sdk v1.0.1 h1:wXxFEWGo7XfXupPwVJvTBOaPBC9FEg0wB8hMNrKk+cA= -go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI= -go.opentelemetry.io/otel/trace v1.0.1 h1:StTeIH6Q3G4r0Fiw34LTokUFESZgIDUr0qIJ7mKmAfw= -go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y= +go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.3.0 h1:Kte45gGM12Ks0pZng7Pi+IFlbbeY287ZpGX0s0G9al8= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.3.0/go.mod h1:PQLM+xJ3EMSZU9rMevmw+4nH1efyp23CW/nD9BlB3sg= +go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI= +go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= +go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY= +go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= @@ -70,9 +253,8 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.12.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw= go.uber.org/dig v1.13.0 h1:bb9lVW3gtpQsNb07d0xL5vFwsjHidPJxaR/zSsbmfVQ= go.uber.org/dig v1.13.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw= -go.uber.org/fx v1.15.0 h1:kcfBpAm98n0ksanyyZLFE/Q3T7yPi13Ge2liu3TxR+A= -go.uber.org/fx v1.15.0/go.mod h1:jI3RazQUhGv5KkpZIRv+kuP4CcgX3fnc0qX8bLnzbx8= -go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/fx v1.16.0 h1:N8i80+X1DCX+qMRiKzM+jPPZiIiyK/bVCysga3+B+1w= +go.uber.org/fx v1.16.0/go.mod h1:OMoT5BnXcOaiexlpjtpE4vcAmzyDKyRs9TRYXCzamx8= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -81,55 +263,295 @@ go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= -go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.20.0 h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc= +go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210903071746-97244b99971b h1:3Dq0eVHn0uaQJmPO+/aYPI/fRMqdrVDbu7MQcku54gg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191030062658-86caa796c7ab/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/influx_exporter/readme.md b/influx_exporter/readme.md deleted file mode 100644 index 7d46b18..0000000 --- a/influx_exporter/readme.md +++ /dev/null @@ -1 +0,0 @@ -Tracer implementation fot influxDB 1.8.* \ No newline at end of file diff --git a/NOTICE.txt b/legal_notices.txt similarity index 81% rename from NOTICE.txt rename to legal_notices.txt index 6088fb0..06fcd44 100644 --- a/NOTICE.txt +++ b/legal_notices.txt @@ -1,64 +1,96 @@ -==== -The file contains information about the dependencies which are used in the software. -==== +INFORMATION ABOUT THIRD-PARTY CODE -github.com/Masterminds/squirrel 1.5.0 -Copyright (C) 2014-2015, Lann Martin -Copyright (C) 2015-2016, Google -Copyright (C) 2015, Matt Farina and Matt Butcher +------- +This file contains information about dependencies which are used in the Klogga. + + +atomic v1.9.0 +Copyright (c) 2016 Uber Technologies, Inc. +----- +Distributed under the terms of the MIT License (MIT) +----- + + +dig v1.13.0 +Copyright (c) 2017-2018 Uber Technologies, Inc. +----- +Distributed under the terms of the MIT License (MIT) +----- + + +fx v1.16.0 +Copyright (c) 2016-2018 Uber Technologies, Inc. ----- -Distributed under the terms of the MIT License +Distributed under the terms of the MIT License (MIT) ----- -influxdata/influxdb1-client Commit b269163 +influxdb1-client v0.0.0-20200827194710-b269163b24ab Copyright (c) 2019 InfluxData ----- -Distributed under the terms of the MIT License +Distributed under the terms of the MIT License (MIT) ----- -jmoiron/sqlx 1.3.4 -Copyright (c) 2013, Jason Moiron +multierr v1.7.0 +Copyright (c) 2017-2021 Uber Technologies, Inc. ----- -Distributed under the terms of the MIT License +Distributed under the terms of the MIT License (MIT) ----- -lib/pq 1.10.2 -Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany +pq v1.10.4 +Copyright (c) 2011-2013, 'pq' Contributors +Portions Copyright (C) 2011 Blake Mizerany ----- -Distributed under the terms of the MIT License +Distributed under the terms of the MIT License (MIT) ----- -satori/go.uuid v1.2.0 -Copyright (C) 2013-2018 by Maxim Bublis +pretty v0.0.0-20200227124842-a10e7caefd8e +Copyright 2012 Keith Rarick ----- -Distributed under the terms of the MIT License +Distributed under the terms of the MIT License (MIT) ----- -stretchr/testify 1.7.0 -Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors +sqlx v1.3.4 +Copyright (c) 2013, Jason Moiron ----- -Distributed under the terms of the MIT License +Distributed under the terms of the MIT License (MIT) ----- -go.uber.org/atomic 1.9.0 -Copyright (c) 2016 Uber Technologies, Inc. +squirrel v1.5.2 +Copyright (C) 2014-2015, Lann Martin +Copyright (C) 2015-2016, Google +Copyright (C) 2015, Matt Farina and Matt Butcher ----- -Distributed under the terms of the MIT License +Distributed under the terms of the MIT License (MIT) ----- -go.uber.org/fx 1.15.0 -Copyright (c) 2016-2018 Uber Technologies, Inc. +testify v1.7.0 +Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors. ----- -Distributed under the terms of the MIT License +Distributed under the terms of the MIT License (MIT) ----- + +text v0.2.0 +Copyright 2012 Keith Rarick +----- +Distributed under the terms of the MIT License (MIT) +----- + + +zap v1.20.0 +Copyright (c) 2016-2017 Uber Technologies, Inc. +----- +Distributed under the terms of the MIT License (MIT) +----- + + == the MIT License == @@ -66,40 +98,120 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -golang/mock 1.6.0 -Copyright 2019 Google LLC + +check.v1 v1.0.0-20200227125254-8fa46927fb4f +Copyright (c) 2010-2013 Gustavo Niemeyer . +----- +Distributed under the terms of the BSD License (two-clause) +----- +This library contains Benchmark.go distributed under the following terms: +Copyright (c) 2012 The Go Authors. All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +errors v0.9.1 +Copyright (c) 2015, Dave Cheney +----- +Distributed under the terms of the BSD License (two-clause) +----- + + +== +The BSD License (two-clause) +== +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +uuid v1.3.0 +Copyright (c) 2009,2014 Google Inc. All rights reserved. +----- +Distributed under the terms of the BSD License (three-clause) +----- + + +== +the BSD License (three-clause) +== +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +client_golang v1.12.1 +Prometheus instrumentation library for Go applications +Copyright 2012-2015 The Prometheus Authors +----- +Distributed under the terms of the Apache License Version 2.0 +----- +This product includes software developed at SoundCloud Ltd. (http://soundcloud.com/). + +The following components are included in this product: + +perks - a fork of https://github.com/bmizerany/perks +https://github.com/beorn7/perks +Copyright 2013-2015 Blake Mizerany, Björn Rabenstein +See https://github.com/beorn7/perks/blob/master/README.md for license details. + +Go support for Protocol Buffers - Google's data interchange format +http://github.com/golang/protobuf/ +Copyright 2010 The Go Authors +See source code for license details. + +Support for streaming Protocol Buffer messages for the Go language (golang). +https://github.com/matttproud/golang_protobuf_extensions +Copyright 2013 Matt T. Proud +Licensed under the Apache License, Version 2.0 + + + +mock v1.6.0 +Alex Reece +Google Inc. ----- Distributed under the terms of the Apache License Version 2.0 ----- -go.opentelemetry.io/otel 1.0.1 +otel v1.3.0 Copyright The OpenTelemetry Authors ----- Distributed under the terms of the Apache License Version 2.0 ----- -go.opentelemetry.io/otel/exporters/stdout/stdouttrace 1.0.1 +sdk v1.3.0 Copyright The OpenTelemetry Authors ----- Distributed under the terms of the Apache License Version 2.0 ----- -go.opentelemetry.io/otel/sdk 1.0.1 +stdouttrace v1.3.0 Copyright The OpenTelemetry Authors ----- Distributed under the terms of the Apache License Version 2.0 ----- -go.opentelemetry.io/otel/trace 1.0.1 +trace v1.3.0 Copyright The OpenTelemetry Authors ----- Distributed under the terms of the Apache License Version 2.0 ----- + +== +the Apache License Version 2.0 +== Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -128,10 +240,25 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS +----- -hashicorp/go-multierror 1.1.1 -https://github.com/hashicorp/go-multierror + +errwrap v1.1.0 +https://github.com/hashicorp/errwrap/tree/v1.1.0 +----- +Distributed under the terms of the Mozilla Public License, version 2.0 +----- + + +go-multierror v1.1.1 +https://github.com/hashicorp/go-multierror/tree/v1.1.1 +----- +Distributed under the terms of the Mozilla Public License, version 2.0 ----- + +== +the Mozilla Public License, version 2.0 +== Mozilla Public License, version 2.0 1. Definitions 1.1. “Contributor” means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. @@ -214,15 +341,4 @@ This Source Code Form is subject to the terms of the Mozilla Public License, v. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - “Incompatible With Secondary Licenses” Notice -This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. ------ - -pkg/errors 0.9.1 -Copyright (c) 2015, Dave Cheney -All rights reserved. ------ -The BSD 2-Clause License -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/otel_exporter/otel_exporter_to_tracer.go b/otel_exporter/otel_exporter_to_tracer.go deleted file mode 100644 index 5b4bf13..0000000 --- a/otel_exporter/otel_exporter_to_tracer.go +++ /dev/null @@ -1,57 +0,0 @@ -package otel_exporter - -import ( - "context" - "go.kl/klogga" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/trace" -) - -// ExporterTracer basic exporter of spans to otel tracer -type ExporterTracer struct { - tracer trace.Tracer -} - -func NewExporterTracer(tracer trace.Tracer) *ExporterTracer { - return &ExporterTracer{tracer: tracer} -} - -func (t *ExporterTracer) Write(ctx context.Context, spans []*klogga.Span) error { - for _, span := range spans { - _, otelSpan := t.tracer.Start( - ctx, span.Name(), - trace.WithTimestamp(span.StartedTs()), - ) - - for k, v := range span.Vals() { - otelSpan.SetAttributes( - attribute.KeyValue{ - Key: attribute.Key(k), - Value: ConvertValue(v), - }, - ) - } - for k, v := range span.Tags() { - otelSpan.SetAttributes( - attribute.KeyValue{ - Key: attribute.Key(k), - Value: ConvertValue(v), - }, - ) - } - if span.HasWarn() { - otelSpan.SetAttributes(attribute.String("warn", span.Warns().Error())) - } - if span.HasErr() { - otelSpan.RecordError(span.Errs()) - otelSpan.SetStatus(codes.Error, "E") - } - otelSpan.End() - } - return nil -} - -func (t *ExporterTracer) Shutdown(context.Context) error { - return nil -} diff --git a/otel_exporter/otel_test.go b/otel_exporter/otel_test.go deleted file mode 100644 index d1b35fa..0000000 --- a/otel_exporter/otel_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package otel_exporter - -import ( - "github.com/stretchr/testify/require" - "go.kl/klogga" - "go.kl/klogga/util/testutil" - "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" - "go.opentelemetry.io/otel/sdk/trace" - _ "go.opentelemetry.io/otel/trace" - "strings" - "testing" - "time" -) - -func TestOtelExporter(t *testing.T) { - sb := strings.Builder{} - exporter, err := stdouttrace.New(stdouttrace.WithWriter(&sb), stdouttrace.WithPrettyPrint()) - require.NoError(t, err) - tp := trace.NewTracerProvider(trace.WithSyncer(exporter)) - defer func() { - require.NoError(t, tp.Shutdown(testutil.Timeout())) - }() - - trs := klogga.NewFactory(NewExporterTracer(tp.Tracer("test123"))).NamedPkg() - span := klogga.StartLeaf(testutil.Timeout()) - span.Tag("key1", "test_key_value") - time.Sleep(400 * time.Millisecond) - trs.Finish(span) - t.Log(span.Stringify()) - - require.Contains(t, sb.String(), "test_key_value") - t.Logf(sb.String()) - -} diff --git a/pg_exporter/pg_conn/pg.sh b/pg_exporter/pg_conn/pg.sh deleted file mode 100644 index b85ab9f..0000000 --- a/pg_exporter/pg_conn/pg.sh +++ /dev/null @@ -1 +0,0 @@ -podman run --name klogga --rm -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres timescale/timescaledb:latest-pg13 diff --git a/pg_exporter/pg_conn/pg.sql b/pg_exporter/pg_conn/pg.sql deleted file mode 100644 index f33c974..0000000 --- a/pg_exporter/pg_conn/pg.sql +++ /dev/null @@ -1,4 +0,0 @@ -select * from audit.pg_conn; - -select * -from audit.error_metrics; \ No newline at end of file diff --git a/pg_exporter/pg_conn/pg_conn.go b/pg_exporter/pg_conn/pg_conn.go deleted file mode 100644 index e82b02b..0000000 --- a/pg_exporter/pg_conn/pg_conn.go +++ /dev/null @@ -1,33 +0,0 @@ -package pg_conn - -import ( - "context" - "github.com/jmoiron/sqlx" - "github.com/pkg/errors" - "go.kl/klogga/pg_exporter" -) - -// PgConnector the simplest PG connector implementation with sqlx -type PgConnector struct { - ConnectionString string - MaxOpenConnections int - MaxIdleConnections int -} - -func (p PgConnector) GetConnection(ctx context.Context) (pg_exporter.Connection, error) { - conn, err := sqlx.ConnectContext(ctx, "postgres", p.ConnectionString) - if err != nil { - return nil, errors.Wrapf(err, "pg connect failed: %s", p.ConnectionString) - } - conn.SetMaxOpenConns(p.MaxOpenConnections) - conn.SetMaxIdleConns(p.MaxIdleConnections) - return conn, nil -} - -func (p PgConnector) Close(context.Context) error { - return nil -} - -func (p PgConnector) GetConnectionRaw(ctx context.Context) (*sqlx.DB, error) { - return sqlx.ConnectContext(ctx, "postgres", p.ConnectionString) -} diff --git a/pg_exporter/pg_conn/pg_integration_test.go b/pg_exporter/pg_conn/pg_integration_test.go deleted file mode 100644 index 9fa7599..0000000 --- a/pg_exporter/pg_conn/pg_integration_test.go +++ /dev/null @@ -1,333 +0,0 @@ -package pg_conn - -import ( - "encoding/json" - "github.com/pkg/errors" - "github.com/stretchr/testify/require" - "go.kl/klogga" - "go.kl/klogga/constants/vals" - "go.kl/klogga/util/testutil" - "testing" -) - -func TestWriteJson(t *testing.T) { - pg, conn := PgConn(t) - - _, err := conn.Exec("DROP TABLE IF EXISTS audit.test_json") - require.NoError(t, err) - - span := klogga.StartLeaf(testutil.Timeout()) - span.SetComponent("test_json") - value := `{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["en-US,en;q=0.9,ru;q=0.8"],"Connection":["keep-alive"],"Content-Length":["0"],"Cookie":["s_fid=145F81CE863075C0-0F188C1885167E1E; s_cc=true; _ga=GA1.1.778486296.1606136302; tipadmin-dev=2195f6f1e24d9a3bafa3aef0f55916be"],"Ik4nnm_yfy":["0AFNS9GstJWK4gYwsSr8wxKqafIGOeljdJ75pwd46QEKBWFkbWluEIT6t4UGGgFbIhRxlsLlosMkyyWktTKm5Zu+EZCVHw=="],"Origin":["http://localhost:1234"],"Referer":["http://localhost:1234/requests/submissions"],"Sec-Ch-Ua":["\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"90\", \"Google Chrome\";v=\"90\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-origin"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"]}` - jj := make(map[string]interface{}) - err = json.Unmarshal([]byte(value), &jj) - require.NoError(t, err) - span.ValAsObj("json_field", jj) - span.Val("bytes_field", []byte("danila")) - - err = pg.Write(testutil.Timeout(), []*klogga.Span{span}) - require.NoError(t, err) - err = pg.Shutdown(testutil.Timeout()) - require.NoError(t, err) - - rows, err := psql.Select("id", "parent", "pkg_class", "json_field", "bytes_field"). - From("audit.test_json"). - RunWith(conn). - Query() - require.NoError(t, err) - hasNext := rows.Next() - require.True(t, hasNext) - require.NoError(t, rows.Err()) - types, err := rows.ColumnTypes() - require.NoError(t, err) - require.Len(t, types, 5) - - require.Equal(t, "JSONB", types[3].DatabaseTypeName()) - require.Equal(t, "BYTEA", types[4].DatabaseTypeName()) -} - -func TestWriteWithoutTags(t *testing.T) { - pg, conn := PgConn(t) - - _, err := conn.Exec("DROP TABLE IF EXISTS audit.test_tags") - require.NoError(t, err) - - span := klogga.StartLeaf(testutil.Timeout()) - span.SetComponent("test_tags") - value := `{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["en-US,en;q=0.9,ru;q=0.8"],"Connection":["keep-alive"],"Content-Length":["0"],"Cookie":["s_fid=145F81CE863075C0-0F188C1885167E1E; s_cc=true; _ga=GA1.1.778486296.1606136302; tipadmin-dev=2195f6f1e24d9a3bafa3aef0f55916be"],"Ik4nnm_yfy":["0AFNS9GstJWK4gYwsSr8wxKqafIGOeljdJ75pwd46QEKBWFkbWluEIT6t4UGGgFbIhRxlsLlosMkyyWktTKm5Zu+EZCVHw=="],"Origin":["http://localhost:1234"],"Referer":["http://localhost:1234/requests/submissions"],"Sec-Ch-Ua":["\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"90\", \"Google Chrome\";v=\"90\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-origin"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"]}` - jj := make(map[string]interface{}) - err = json.Unmarshal([]byte(value), &jj) - require.NoError(t, err) - - err = pg.Write(testutil.Timeout(), []*klogga.Span{span}) - require.NoError(t, err) - err = pg.Shutdown(testutil.Timeout()) - require.NoError(t, err) - - rows, err := psql. - Select("duration"). - From("audit.test_tags"). - RunWith(conn). - Query() - require.NoError(t, err) - - hasNext := rows.Next() - require.True(t, hasNext) - require.NoError(t, rows.Err()) -} - -func TestWriteError(t *testing.T) { - pg, conn := PgConn(t) - - _, err := conn.Exec("DROP TABLE IF EXISTS audit.test_error") - require.NoError(t, err) - - span := klogga.StartLeaf(testutil.Timeout()) - span.SetComponent("test_error") - span.ErrVoid(errors.New("error-test")) - - err = pg.Write(testutil.Timeout(), []*klogga.Span{span}) - require.NoError(t, err) - err = pg.Shutdown(testutil.Timeout()) - require.NoError(t, err) - - rows, err := psql. - Select("duration"). - From("audit.test_error"). - RunWith(conn). - Query() - require.NoError(t, err) - - hasNext := rows.Next() - require.True(t, hasNext) - require.NoError(t, rows.Err()) -} - -func TestIncrementalSpan(t *testing.T) { - pg, conn := PgConn(t) - - _, err := conn.Exec("DROP TABLE IF EXISTS audit.test_incremental") - require.NoError(t, err) - - span := klogga.StartLeaf(testutil.Timeout()) - span.SetComponent("test_incremental") - - span.Tag("login", "danila") - - err = pg.Write(testutil.Timeout(), []*klogga.Span{span}) - require.NoError(t, err) - - row := psql.Select("login").From("audit.test_incremental"). - RunWith(conn).QueryRow() - l := "" - err = row.Scan(&l) - require.NoError(t, err) - require.Equal(t, "danila", l) - - span = klogga.StartLeaf(testutil.Timeout()) - span.SetComponent("test_incremental") - span.Val(vals.Count, 22) - err = pg.Write(testutil.Timeout(), []*klogga.Span{span}) - require.NoError(t, err) - - rows, err := psql.Select("count").From("audit.test_incremental"). - RunWith(conn).Query() - require.NoError(t, err) - require.True(t, rows.Next()) - require.True(t, rows.Next()) - var c *int - err = rows.Scan(&c) - require.NoError(t, err) - require.Equal(t, 22, *c) -} - -// -//func TestWriteByTicker(t *testing.T) { -// tc := NewTestContext(t) -// defer tc.Shutdown() -// -// cfg := testConf(tc.schema) -// cfg.WriteBatchInterval = time.Second / 10 -// cfg.BatchSize = 10 -// trs := New(cfg, tc.cf, testutil.NewTracer()) -// trs.cfg.BatchSize = 2 -// require.NoError(t, trs.Start(testutil.Timeout())) -// -// span, _ := klogga.Start(testutil.Timeout()) -// span.SetComponent("component") -// span.Tag("tag", "val") -// trs.Write(span) -// -// time.Sleep(time.Second / 5) -// -// rows := tc.Select("component", "tag") -// require.Len(t, rows, 1) -// -// require.NoError(t, trs.Stop(testutil.Timeout())) -//} -// -//func TestWriteToFinishedTracer(t *testing.T) { -// tc := NewTestContext(t) -// defer tc.Shutdown() -// -// trs := New(testConf(tc.schema), tc.cf, testutil.NewTracer()) -// require.NoError(t, trs.Start(testutil.Timeout())) -// -// span, _ := klogga.Start(testutil.Timeout()) -// span.SetComponent("component") -// require.NoError(t, trs.Stop(testutil.Timeout())) -// trs.Write(span) -//} -// -//func TestWriteValAsJson(t *testing.T) { -// tc := NewTestContext(t) -// defer tc.Shutdown() -// -// trs := New(testConf(tc.schema), tc.cf, testutil.NewTracer()) -// require.NoError(t, trs.Start(testutil.Timeout())) -// -// type T struct{ Name string } -// val := T{Name: "xxx"} -// span, _ := klogga.Start(testutil.Timeout()) -// span.SetComponent("component") -// span.ValAsJson("val", val) -// -// trs.Write(span) -// require.NoError(t, trs.Stop(testutil.Timeout())) -// rows := tc.Select("component", "val") -// v, ok := (rows[0]["val"]).(*interface{}) -// require.True(t, ok) -// _, ok = (*v).([]byte) -// require.True(t, ok) -//} -// -//func TestBulkInsertMultipleTables(t *testing.T) { -// tc := NewTestContext(t) -// defer tc.Shutdown() -// -// trs := New(testConf(tc.schema), tc.cf, testutil.NewTracer()) -// trs.cfg.BatchSize = 2 -// require.NoError(t, trs.Start(testutil.Timeout())) -// -// for i := 0; i < 3; i++ { -// span1, _ := klogga.Start(testutil.Timeout()) -// span1.SetComponent("component1") -// span1.Tag("x", "x") -// trs.Write(span1) -// -// span2, _ := klogga.Start(testutil.Timeout()) -// span2.SetComponent("component2") -// span2.Tag("x", "x") -// trs.Write(span2) -// } -// -// require.NoError(t, trs.Stop(testutil.Timeout())) -// -// rows := tc.Select("component1", "x") -// require.Len(t, rows, 3) -// rows = tc.Select("component2", "x") -// require.Len(t, rows, 3) -//} -// -//func TestAlterTable(t *testing.T) { -// tc := NewTestContext(t) -// defer tc.Shutdown() -// -// trs := New(testConf(tc.schema), tc.cf, testutil.NewTracer()) -// require.NoError(t, trs.Start(testutil.Timeout())) -// -// span1, _ := klogga.Start(testutil.Timeout()) -// span1.SetComponent("component") -// span1.Tag("tag1", "val1") -// trs.Write(span1) -// span2, _ := klogga.Start(testutil.Timeout()) -// span2.SetComponent("component") -// span2.Tag("tag2", "val2") -// trs.Write(span2) -// -// require.NoError(t, trs.Stop(testutil.Timeout())) -// -// rows := tc.Select("component", "tag1", "tag2") -// require.Len(t, rows, 2) -// require.Empty(t, rows[0]["tag2"]) -// require.Empty(t, rows[1]["tag1"]) -// require.NotEmpty(t, rows[0]["tag1"]) -// require.NotEmpty(t, rows[1]["tag2"]) -//} -// -//func TestWritePgTypes(t *testing.T) { -// tc := NewTestContext(t) -// defer tc.Shutdown() -// -// trs := New(testConf(tc.schema), tc.cf, testutil.NewTracer()) -// require.NoError(t, trs.Start(testutil.Timeout())) -// -// type T struct{ Name string } -// span, _ := klogga.Start(testutil.Timeout()) -// span.SetComponent("component") -// span. -// Tag("int", 11). -// Tag("int64", int64(11)). -// Tag("bool", true). -// Tag("bytes", []byte{0x01}). -// Tag("float", 1.1). -// Tag("string", "string"). -// Tag("time_time", time.Now()). -// Tag("dur", time.Second). -// Tag("struct", T{Name: "xxx"}) -// -// trs.Write(span) -// require.NoError(t, trs.Stop(testutil.Timeout())) -// -// rows := tc.Select("component", "dur") -// require.Len(t, rows, 1) -//} -// -//func TestIncompatibleSchemaInOneBatch(t *testing.T) { -// tc := NewTestContext(t) -// defer tc.Shutdown() -// -// trs := New(testConf(tc.schema), tc.cf, testutil.NewTracer()) -// trs.cfg.BatchSize = 2 -// require.NoError(t, trs.Start(testutil.Timeout())) -// -// span1, _ := klogga.Start(testutil.Timeout()) -// span1.SetComponent("component") -// span1.Tag("tag", "val") -// span2, _ := klogga.Start(testutil.Timeout()) -// span2.SetComponent("component") -// span2.Tag("tag", int64(11)) -// -// trs.Write(span1) -// trs.Write(span2) -// -// require.NoError(t, trs.Stop(testutil.Timeout())) -// rows := tc.Select("component", "tag") -// require.Len(t, rows, 1) -// -// rows = tc.Select(errorMetricsTableName, "error") -// require.Len(t, rows, 1) -//} -// -//func TestIncompatibleSchemaInMultipleBatches(t *testing.T) { -// tc := NewTestContext(t) -// defer tc.Shutdown() -// -// trs := New(testConf(tc.schema), tc.cf, testutil.NewTracer()) -// require.NoError(t, trs.Start(testutil.Timeout())) -// -// span1 := klogga.StartLeaf(testutil.Timeout()).Tag("tag", "val") -// span1.SetComponent("component") -// span2 := klogga.StartLeaf(testutil.Timeout()).Tag("tag", int64(11)) -// span2.SetComponent("component") -// -// trs.Write(span1) -// trs.Write(span2) -// -// require.NoError(t, trs.Stop(testutil.Timeout())) -// rows := tc.Select("component", "tag") -// require.Len(t, rows, 1) -// -// rows = tc.Select(errorMetricsTableName, "error") -// require.Len(t, rows, 1) -//} diff --git a/pg_exporter/pg_conn/pg_testfixture_test.go b/pg_exporter/pg_conn/pg_testfixture_test.go deleted file mode 100644 index c519d3e..0000000 --- a/pg_exporter/pg_conn/pg_testfixture_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package pg_conn - -import ( - "github.com/Masterminds/squirrel" - "github.com/jmoiron/sqlx" - "github.com/stretchr/testify/require" - "go.kl/klogga" - golog "go.kl/klogga/golog_exporter" - "go.kl/klogga/pg_exporter" - "go.kl/klogga/util/testutil" - "testing" -) - -var psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar) - -func PgConn(t *testing.T) (*pg_exporter.Exporter, *sqlx.DB) { - if testing.Short() { - t.Skip("longer integration test") - } - pgConn := &PgConnector{ - ConnectionString: testutil.IntegrationEnv(t, "PG_CONNECTION_STRING"), - } - conn, err := pgConn.GetConnectionRaw(testutil.Timeout()) - require.NoError(t, err) - - _, err = conn.Exec("create schema if not exists audit") - require.NoError(t, err) - - return pg_exporter.New( - &pg_exporter.Conf{}, - pgConn, - klogga.NewTestErrTracker(t, klogga.NewFactory(golog.New(nil)).NamedPkg()), - ), conn -} diff --git a/pg_exporter/pg_exporter_mock.go b/pg_exporter/pg_exporter_mock.go deleted file mode 100644 index 6428878..0000000 --- a/pg_exporter/pg_exporter_mock.go +++ /dev/null @@ -1,143 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: pg_exporter.go - -// Package pg_exporter is a generated GoMock package. -package pg_exporter - -import ( - context "context" - sql "database/sql" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" -) - -// MockConnection is a mock of Connection interface. -type MockConnection struct { - ctrl *gomock.Controller - recorder *MockConnectionMockRecorder -} - -// MockConnectionMockRecorder is the mock recorder for MockConnection. -type MockConnectionMockRecorder struct { - mock *MockConnection -} - -// NewMockConnection creates a new mock instance. -func NewMockConnection(ctrl *gomock.Controller) *MockConnection { - mock := &MockConnection{ctrl: ctrl} - mock.recorder = &MockConnectionMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockConnection) EXPECT() *MockConnectionMockRecorder { - return m.recorder -} - -// BeginTx mocks base method. -func (m *MockConnection) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BeginTx", ctx, opts) - ret0, _ := ret[0].(*sql.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// BeginTx indicates an expected call of BeginTx. -func (mr *MockConnectionMockRecorder) BeginTx(ctx, opts interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeginTx", reflect.TypeOf((*MockConnection)(nil).BeginTx), ctx, opts) -} - -// ExecContext mocks base method. -func (m *MockConnection) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { - m.ctrl.T.Helper() - varargs := []interface{}{ctx, query} - for _, a := range args { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "ExecContext", varargs...) - ret0, _ := ret[0].(sql.Result) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ExecContext indicates an expected call of ExecContext. -func (mr *MockConnectionMockRecorder) ExecContext(ctx, query interface{}, args ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{ctx, query}, args...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecContext", reflect.TypeOf((*MockConnection)(nil).ExecContext), varargs...) -} - -// QueryContext mocks base method. -func (m *MockConnection) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { - m.ctrl.T.Helper() - varargs := []interface{}{ctx, query} - for _, a := range args { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "QueryContext", varargs...) - ret0, _ := ret[0].(*sql.Rows) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// QueryContext indicates an expected call of QueryContext. -func (mr *MockConnectionMockRecorder) QueryContext(ctx, query interface{}, args ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{ctx, query}, args...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryContext", reflect.TypeOf((*MockConnection)(nil).QueryContext), varargs...) -} - -// MockConnector is a mock of Connector interface. -type MockConnector struct { - ctrl *gomock.Controller - recorder *MockConnectorMockRecorder -} - -// MockConnectorMockRecorder is the mock recorder for MockConnector. -type MockConnectorMockRecorder struct { - mock *MockConnector -} - -// NewMockConnector creates a new mock instance. -func NewMockConnector(ctrl *gomock.Controller) *MockConnector { - mock := &MockConnector{ctrl: ctrl} - mock.recorder = &MockConnectorMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockConnector) EXPECT() *MockConnectorMockRecorder { - return m.recorder -} - -// Close mocks base method. -func (m *MockConnector) Close(ctx context.Context) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Shutdown", ctx) - ret0, _ := ret[0].(error) - return ret0 -} - -// Close indicates an expected call of Close. -func (mr *MockConnectorMockRecorder) Close(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockConnector)(nil).Close), ctx) -} - -// GetConnection mocks base method. -func (m *MockConnector) GetConnection(ctx context.Context) (Connection, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetConnection", ctx) - ret0, _ := ret[0].(Connection) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetConnection indicates an expected call of GetConnection. -func (mr *MockConnectorMockRecorder) GetConnection(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConnection", reflect.TypeOf((*MockConnector)(nil).GetConnection), ctx) -} diff --git a/span.go b/span.go index 91fefd1..53e7731 100644 --- a/span.go +++ b/span.go @@ -4,10 +4,10 @@ import ( "context" "encoding/json" "fmt" + "github.com/KasperskyLab/klogga/constants" + "github.com/KasperskyLab/klogga/util/errs" + "github.com/KasperskyLab/klogga/util/reflectutil" "github.com/pkg/errors" - "go.kl/klogga/constants/vals" - "go.kl/klogga/util/errs" - "go.kl/klogga/util/reflectutil" "strings" "time" ) @@ -16,9 +16,11 @@ import ( // Span is a (TODO) serializable // and independent of the way it is exported (traced) to any storage type Span struct { - id TraceId - startedTs time.Time - component ComponentName + id SpanID + traceID TraceID + startedTs time.Time + finishedTs time.Time + component ComponentName name string // usually a calling func name is used className string // name of the struct @@ -29,7 +31,7 @@ type Span struct { host string // machine name where span was created parent *Span - parentID TraceId + parentID SpanID duration time.Duration @@ -42,81 +44,114 @@ type Span struct { errs error warns error deferErrs error - - finished bool } // Start preferred way to start a new span, automatically sets basic span fields like class, name, host -func Start(ctx context.Context) (*Span, context.Context) { - packageName, className, funcName := reflectutil.GetPackageClassFunc() - span := &Span{ - id: NewTraceId(), - packageName: packageName, - className: className, - name: funcName, +func Start(ctx1 context.Context, opts ...SpanOption) (span *Span, ctx context.Context) { + span = &Span{ + id: NewSpanID(), host: host, - startedTs: time.Now().UTC(), + startedTs: time.Now(), tags: map[string]interface{}{}, vals: map[string]interface{}{}, propagatedTags: map[string]interface{}{}, } - if p := CtxActiveSpan(ctx); p != nil { + if p := CtxActiveSpan(ctx1); p != nil { span.parent = p span.parentID = p.id + span.traceID = p.traceID for k, v := range p.propagatedTags { span.propagatedTags[k] = v span.Tag(k, v) } + } else { + span.traceID = NewTraceID() + } + + for _, opt := range opts { + opt.apply(span) + } + + if span.packageName == "" || span.className == "" || span.name == "" { + packageName, className, funcName := reflectutil.GetPackageClassFunc() + if span.packageName == "" { + span.packageName = packageName + } + if span.className == "" { + span.className = className + } + if span.name == "" { + span.name = funcName + } } - return span, context.WithValue(ctx, activeSpanKey, span) + return span, context.WithValue(ctx1, activeSpanKey{}, span) } // StartLeaf start new span without returning resulting context i.e. no child spans possibility -func StartLeaf(ctx context.Context) *Span { +func StartLeaf(ctx context.Context, opts ...SpanOption) (span *Span) { packageName, className, funcName := reflectutil.GetPackageClassFunc() - span, _ := Start(ctx) + span, _ = Start(ctx, append([]SpanOption{ + WithName(funcName), + WithPackageClass(packageName, className), + }, opts...)...) span.packageName = packageName span.className = className - span.name = funcName + return span +} + +// Message is the simplest way to start a span, in the shortest way possible +// it doesn't use context, and doesn't return one. +// It is strongly discouraged to use Message unless for testing purposes. +func Message(message string, opts ...SpanOption) *Span { + packageName, className, funcName := reflectutil.GetPackageClassFunc() + span, _ := Start(context.Background(), append([]SpanOption{ + WithPackageClass(packageName, className), + WithName(funcName), + }, opts...)...) + span.Message(message) return span } // StartFromParentID starts new span with externally defined parent span ID -func StartFromParentID(ctx context.Context, parentSpanID TraceId) (*Span, context.Context) { +// Deprecated: use SpanOptions +func StartFromParentID(ctx context.Context, parentSpanID SpanID, traceID TraceID) (*Span, context.Context) { p, c, f := reflectutil.GetPackageClassFunc() - span, ctx := Start(ctx) - span.packageName = p - span.className = c - span.name = f + span, ctx := Start(ctx, WithPackageClass(p, c), WithName(f)) span.parentID = parentSpanID + span.traceID = traceID return span, ctx } -func (s Span) ID() TraceId { +func (s *Span) ID() SpanID { return s.id } -func (s Span) Parent() *Span { +func (s *Span) TraceID() TraceID { + return s.traceID +} + +func (s *Span) Parent() *Span { return s.parent } func (s *Span) Stop() { // no need to sync this, as the race won't matter - if !s.finished { - s.finished = true - s.duration = time.Since(s.startedTs) + if !s.finishedTs.IsZero() { + return } + s.finishedTs = time.Now() + s.duration = s.finishedTs.Sub(s.startedTs) } func (s *Span) IsFinished() bool { - return s.finished + return !s.finishedTs.IsZero() } -func (s *Span) ParentID() TraceId { +func (s *Span) ParentID() SpanID { if s == nil { - return TraceId{} + return SpanID{} } return s.parentID } @@ -185,7 +220,9 @@ type ObjectVal struct { obj interface{} } -func NewObjectVal(obj interface{}) *ObjectVal { +// ValObject explicitly indicates that the underlying value is a nested object, +// to be stored in a complex field like jsonb +func ValObject(obj interface{}) *ObjectVal { return &ObjectVal{obj: obj} } @@ -198,17 +235,46 @@ func (o ObjectVal) String() string { return string(bb) } -// ValAsObj same any object as value. Different storages will try to use -// their corresponding type to store it -// string representation is JSON by default +type StringAsJSONVal struct { + v []byte +} + +// ValJson Use to directly inform klogga that is value is a valid json +// and no conversion is needed +func ValJson(v string) *ObjectVal { + //return &ObjectVal{obj: StringAsJSONVal{v: []byte(v)}} + jj := json.RawMessage(v) + if v == "" { + jj = json.RawMessage(nil) + } + return &ObjectVal{obj: jj} +} + +func (o StringAsJSONVal) MarshalJSON() ([]byte, error) { + if len(o.v) == 0 { + return []byte("{}"), nil + } + return o.v, nil +} + +func (o StringAsJSONVal) String() string { + return string(o.v) +} + +// ValAsObj shorthand for .Val(key, klogga.ValObject(value)) func (s *Span) ValAsObj(key string, value interface{}) *Span { - if value == nil { + if reflectutil.IsNil(value) { return s } - s.Val(key, NewObjectVal(value)) + s.Val(key, ValObject(value)) return s } +// ValAsJson shorthand for .Val(key, klogga.ValJson(value)) +func (s *Span) ValAsJson(key string, value string) *Span { + return s.Val(key, ValJson(value)) +} + // GlobalTag set the tag that is also propagated to all child spans // not thread safe func (s *Span) GlobalTag(key string, value interface{}) *Span { @@ -257,6 +323,7 @@ func (s *Span) ErrRecover(rec interface{}, stackBytes []byte) *Span { if err, ok := rec.(error); ok { s.ErrVoid(errors.Wrap(err, string(stackBytes))) } else { + //nolint:goerr113 // special case when recovering panic with alternative type s.ErrVoid(errors.Wrap(fmt.Errorf("%v", rec), string(stackBytes))) } return s @@ -364,7 +431,7 @@ func (s *Span) Stack() []*Span { // Duration returns current duration for running span // returns total duration for stopped span func (s *Span) Duration() time.Duration { - if s.finished { + if s.IsFinished() { return s.duration } return time.Since(s.startedTs) @@ -385,7 +452,7 @@ func (s *Span) HasDeferErr() bool { // Stringify full span data string // to be used in text tracers // deliberately ignores host field -func (s *Span) Stringify() string { +func (s *Span) Stringify(endWith ...string) string { if s == nil { return "" } @@ -441,9 +508,14 @@ func (s *Span) Stringify() string { sb.WriteString(fmt.Sprintf("; id: %s", s.id)) } if !s.parentID.IsZero() { - sb.WriteString(fmt.Sprintf("; %s: %s", vals.ParentSpanId, s.parentID)) + sb.WriteString(fmt.Sprintf("; %s: %s", constants.ParentSpanID, s.parentID)) + } + if !s.traceID.IsZero() { + sb.WriteString(fmt.Sprintf("; trace: %s", s.traceID)) + } + for _, end := range endWith { + sb.WriteString(end) } - return sb.String() } @@ -463,8 +535,9 @@ func (s *Span) Json() ([]byte, error) { } jsonStruct := struct { - ID TraceId - ParentID TraceId + ID SpanID + ParentID SpanID + TraceID TraceID Ts string Level string PackageClass string @@ -478,6 +551,7 @@ func (s *Span) Json() ([]byte, error) { }{ ID: s.id, ParentID: s.parentID, + TraceID: s.traceID, Ts: s.startedTs.Format(TimestampLayout), Level: s.EWState(), PackageClass: s.PackageClass(), @@ -492,7 +566,7 @@ func (s *Span) Json() ([]byte, error) { return json.Marshal(&jsonStruct) } -// FlushTo accept tracer and call Write +// FlushTo accept tracer and call trs.Finish, shorthand for chaining func (s *Span) FlushTo(trs Tracer) { trs.Finish(s) } @@ -503,7 +577,7 @@ func CreateErrSpanFrom(ctx context.Context, span *Span) *Span { return nil } - errSpan, _ := StartFromParentID(ctx, span.ID()) + errSpan := StartLeaf(ctx, WithTraceID(span.TraceID())) errSpan.parent = span errSpan.startedTs = span.StartedTs() errSpan.Tag("component", span.Component()) diff --git a/span_context.go b/span_context.go index 8cdeaa2..ad721ee 100644 --- a/span_context.go +++ b/span_context.go @@ -2,15 +2,13 @@ package klogga import "context" -type contextKey struct{} - -var activeSpanKey = contextKey{} +type activeSpanKey struct{} // CtxActiveSpan returns current active span from context // (for new spans, current span is a parent span) // careful no to Write this span, parent call should do it func CtxActiveSpan(ctx context.Context) *Span { - if parentObj := ctx.Value(activeSpanKey); parentObj != nil { + if parentObj := ctx.Value(activeSpanKey{}); parentObj != nil { if _parent, ok := parentObj.(*Span); ok { return _parent } diff --git a/span_opt.go b/span_opt.go new file mode 100644 index 0000000..1ca5418 --- /dev/null +++ b/span_opt.go @@ -0,0 +1,87 @@ +package klogga + +import "time" + +type SpanOption interface { + apply(*Span) +} + +type withTimestampOption struct { + ts time.Time +} + +// WithTimestamp make span to have started with custom timestamp +// to create a done span, use WithDone +func WithTimestamp(ts time.Time) SpanOption { + return &withTimestampOption{ts: ts} +} + +func (o withTimestampOption) apply(span *Span) { + span.startedTs = o.ts +} + +type withNameOption struct { + name string +} + +func WithName(name string) SpanOption { + return &withNameOption{name: name} +} + +func (o withNameOption) apply(span *Span) { + span.name = o.name +} + +type withTraceIDOption struct { + traceID TraceID +} + +func WithTraceID(traceID TraceID) SpanOption { + return &withTraceIDOption{traceID: traceID} +} + +func (o withTraceIDOption) apply(span *Span) { + span.traceID = o.traceID +} + +type withParentSpanIDOption struct { + parentSpanID SpanID +} + +func WithParentSpanID(parentSpanID SpanID) SpanOption { + return &withParentSpanIDOption{parentSpanID: parentSpanID} +} + +func (o withParentSpanIDOption) apply(span *Span) { + span.parentID = o.parentSpanID +} + +type withDurationOption struct { + ts time.Time + duration time.Duration +} + +// WithDone make already finished span +func WithDone(ts time.Time, duration time.Duration) SpanOption { + return &withDurationOption{ts: ts, duration: duration} +} + +func (o withDurationOption) apply(span *Span) { + span.startedTs = o.ts + span.duration = o.duration + span.finishedTs = o.ts.Add(o.duration) +} + +type withPackageClassOption struct { + p, c string +} + +// WithPackageClass overrides reflection-retrieved package and class +func WithPackageClass(p, c string) SpanOption { + return &withPackageClassOption{p: p, c: c} +} + +func (o withPackageClassOption) apply(span *Span) { + span.packageName = o.p + span.className = o.c +} diff --git a/span_test.go b/span_test.go index 111c853..dc40ae6 100644 --- a/span_test.go +++ b/span_test.go @@ -2,12 +2,13 @@ package klogga import ( "context" + "github.com/KasperskyLab/klogga/constants/vals" + "github.com/KasperskyLab/klogga/util/testutil" "github.com/golang/mock/gomock" "github.com/pkg/errors" "github.com/stretchr/testify/require" - "go.kl/klogga/constants/vals" - "go.kl/klogga/util/testutil" "testing" + "time" ) func TestSpanString(t *testing.T) { @@ -27,6 +28,15 @@ func TestSpanString(t *testing.T) { t.Log("str:", str) } +func TestSpanDuration(t *testing.T) { + span := StartLeaf(context.Background()) + time.Sleep(10 * time.Millisecond) + span.Stop() + require.True(t, span.IsFinished()) + t.Log("duration", span.Duration()) + require.InDelta(t, 10, span.Duration().Milliseconds(), 20) +} + func TestSpanIntAsString(t *testing.T) { span := StartLeaf(context.Background()) span.Tag("int_value", 111) @@ -67,14 +77,7 @@ func TestWriterNoParent(t *testing.T) { t.Logf("str: %s", str) } -func TestSpanStop(t *testing.T) { - span := StartLeaf(context.Background()) - span.Stop() - require.InDelta(t, 0, span.Duration().Milliseconds(), 10) -} - type La struct { - cl string } //go:noinline @@ -129,13 +132,20 @@ func TestNoErrorSpan(t *testing.T) { } func TestSpanIdString(t *testing.T) { - id := NewTraceId() - span, _ := StartFromParentID(testutil.Timeout(), id) + span := StartLeaf(testutil.Timeout()) + spanStr := span.Stringify() + require.Contains(t, spanStr, "id") + require.Contains(t, spanStr, span.ID().String()) +} + +func TestParentSpanIdString(t *testing.T) { + parentID := NewSpanID() + span := StartLeaf(testutil.Timeout(), WithParentSpanID(parentID)) spanStr := span.Stringify() require.Contains(t, spanStr, "id") require.Contains(t, spanStr, span.ID().String()) require.Contains(t, spanStr, "parent_id") - require.Contains(t, spanStr, id.String()) + require.Contains(t, spanStr, parentID.String()) } func TestSpanWarn(t *testing.T) { @@ -176,10 +186,11 @@ func TestMapObjectNesting(t *testing.T) { }, "int_val": 111, } - span.ValAsObj("obj", mapObj) + span.ValAsObj(vals.ResponseBody, mapObj) spanStr := span.Stringify() t.Log("span", spanStr) + require.Contains(t, spanStr, vals.ResponseBody) require.Contains(t, spanStr, "obj_struct") require.Contains(t, spanStr, "danila") require.Contains(t, spanStr, "int_val") @@ -222,3 +233,63 @@ func TestIntValue(t *testing.T) { str := span.Stringify() require.Contains(t, str, "count:'444'") } + +func TestInitHostname(t *testing.T) { + InitHostname() + span := StartLeaf(context.Background()) + + require.NotEmpty(t, span.Host()) +} + +func TestSetHostname(t *testing.T) { + SetHostname("test_host") + span := StartLeaf(context.Background()) + + require.Equal(t, "test_host", span.Host()) +} + +func TestOptions(t *testing.T) { + dt, _ := time.Parse(TimestampLayout, "2006-01-02 15:04:05.000") + tt := NewTraceID() + span := StartLeaf( + context.Background(), + WithTraceID(tt), + WithName("danilas"), + WithTimestamp(dt), + ) + res := span.Stringify() + t.Log("span:", res) + require.Contains(t, res, "2006-01-02") + require.Contains(t, res, "danilas") + require.Contains(t, res, tt.String()) +} + +func TestMessageBasic(t *testing.T) { + span := Message("lalala") + res := span.Stringify() + require.Contains(t, res, "lalala") + require.Contains(t, res, " I ") +} + +type impl struct { +} + +func (i impl) DoSomething() { +} + +func TestPgValueJsonbNil(t *testing.T) { + span := StartLeaf(testutil.Timeout()) + + type tester interface { + DoSomething() + } + zzNil := func() tester { + var rr *impl + return rr + }() + + span.ValAsObj("is_nil", zzNil) + + _, ok1 := span.Vals()["is_nil"] + require.False(t, ok1) +} diff --git a/temporal_adapter/logger_v2.go b/temporal_adapter/logger_v2.go deleted file mode 100644 index 1100f3c..0000000 --- a/temporal_adapter/logger_v2.go +++ /dev/null @@ -1,50 +0,0 @@ -package temporal_adapter - -import ( - "context" - "fmt" - "go.kl/klogga" - "go.kl/klogga/util/stringutil" -) - -// TemporalLoggerV2Adapter basic adapter of temporal logger to spans -type TemporalLoggerV2Adapter struct { - trs klogga.Tracer - ctx context.Context -} - -func NewTemporalLoggerV2Adapter(trs klogga.Factory, ctx context.Context) *TemporalLoggerV2Adapter { - return &TemporalLoggerV2Adapter{trs: trs.Named("temporal"), ctx: ctx} -} - -func (t TemporalLoggerV2Adapter) trace(level klogga.LogLevel, msg string, keyVals ...interface{}) { - span := klogga.StartLeaf(t.ctx).Level(level) - defer t.trs.Finish(span) - - var key string - for i, keyVal := range keyVals { - if i%2 == 0 { - key = fmt.Sprintf("%v", keyVal) - key = stringutil.ToSnakeCase(key) - continue - } - span.Val(key, keyVal) - } - span.Message(msg) -} - -func (t TemporalLoggerV2Adapter) Debug(msg string, keyvals ...interface{}) { - t.trace(klogga.Info, msg, keyvals...) -} - -func (t TemporalLoggerV2Adapter) Info(msg string, keyvals ...interface{}) { - t.trace(klogga.Info, msg, keyvals...) -} - -func (t TemporalLoggerV2Adapter) Warn(msg string, keyvals ...interface{}) { - t.trace(klogga.Warn, msg, keyvals...) -} - -func (t TemporalLoggerV2Adapter) Error(msg string, keyvals ...interface{}) { - t.trace(klogga.Error, msg, keyvals...) -} diff --git a/test_log_exporter/test_log_exporter.go b/test_log_exporter/test_log_exporter.go deleted file mode 100644 index a42e013..0000000 --- a/test_log_exporter/test_log_exporter.go +++ /dev/null @@ -1,26 +0,0 @@ -package test_log_exporter - -import ( - "context" - "go.kl/klogga" - "testing" -) - -type TestLogExporter struct { - t *testing.T -} - -func NewTestLogExporter(t *testing.T) *TestLogExporter { - return &TestLogExporter{t: t} -} - -func (e TestLogExporter) Write(ctx context.Context, spans []*klogga.Span) error { - for _, span := range spans { - e.t.Logf(span.Stringify()) - } - return nil -} - -func (e TestLogExporter) Shutdown(context.Context) error { - return nil -} diff --git a/testdata/testdata.go b/testdata/testdata.go index a27bda3..8d4a50d 100644 --- a/testdata/testdata.go +++ b/testdata/testdata.go @@ -2,8 +2,8 @@ package testdata import ( "context" + "github.com/KasperskyLab/klogga" "github.com/pkg/errors" - "go.kl/klogga" ) func DataBasicSpan() *klogga.Span { diff --git a/traceid.go b/traceid.go index 0532e3a..e39a48e 100644 --- a/traceid.go +++ b/traceid.go @@ -1,30 +1,34 @@ package klogga import ( + "crypto/rand" "encoding/base64" + "encoding/json" + "github.com/google/uuid" "github.com/pkg/errors" - "github.com/satori/go.uuid" ) -type TraceId uuid.UUID +type TraceID uuid.UUID -func NewTraceId() TraceId { - return TraceId(uuid.NewV4()) -} +const TraceIDSize = 16 -func (t TraceId) IsZero() bool { - return uuid.UUID(t) == uuid.Nil +func NewTraceID() TraceID { + return TraceID(uuid.New()) } -func (t TraceId) Bytes() []byte { - return uuid.UUID(t).Bytes() +func (t TraceID) IsZero() bool { + return t == TraceID{} } -func (t TraceId) AsUUID() uuid.UUID { +func (t TraceID) AsUUID() uuid.UUID { return uuid.UUID(t) } -func (t TraceId) AsNullableUUID() *uuid.UUID { +func (t TraceID) Bytes() []byte { + return t[:] +} + +func (t TraceID) AsNullableUUID() *uuid.UUID { if t.IsZero() { return nil } @@ -32,54 +36,165 @@ func (t TraceId) AsNullableUUID() *uuid.UUID { return &u } -func TraceIdFromBytes(bb []byte) (res TraceId, err error) { - if len(bb) != uuid.Size { - return TraceId{}, errors.Errorf("traceID must be %v long, got %v", uuid.Size, len(bb)) +func TraceIDFromBytes(bb []byte) (TraceID, error) { + if len(bb) != TraceIDSize { + return TraceID{}, errors.Errorf("TraceIDFromString: wrong TraceID size %v expected %v", len(bb), TraceIDSize) } - copy(res[:], bb) - return res, nil + res, err := uuid.ParseBytes(bb) + return TraceID(res), err } -func TraceIdFromBytesOrZero(bb []byte) TraceId { - res, err := TraceIdFromBytes(bb) +func TraceIDFromBytesOrZero(bb []byte) TraceID { + res, err := uuid.ParseBytes(bb) if err != nil { - return TraceId{} + return TraceID{} } - return res + return TraceID(res) } -func ParseTraceId(traceId string) (res TraceId, err error) { - bytes, err := base64.RawURLEncoding.DecodeString(traceId) +func TraceIDFromString(traceID string) (res TraceID, err error) { + bytes, err := base64.RawURLEncoding.DecodeString(traceID) if err != nil { - return TraceId{}, err + return TraceID{}, err + } + if len(bytes) == 0 { + return TraceID{}, nil } - if len(bytes) != uuid.Size { - return TraceId{}, errors.Errorf("wrong traceId size %v expected %v", len(bytes), uuid.Size) + if len(bytes) != len(res) { + return TraceID{}, errors.Errorf("TraceIDFromString: wrong TraceID size %v expected %v", len(bytes), TraceIDSize) } copy(res[:], bytes) return res, err } -// default storage and string format for TraceId is base64! -func (t TraceId) String() string { +// default storage and string format for TraceID is base64! +func (t TraceID) String() string { if t.IsZero() { return "" } return base64.RawURLEncoding.EncodeToString(t[:]) } -func (t TraceId) MarshalText() ([]byte, error) { - - if str := t.String(); str == "" { +func (t TraceID) MarshalText() ([]byte, error) { + if t.IsZero() { return nil, nil } return []byte(base64.RawURLEncoding.EncodeToString(t[:])), nil } -//func (t TraceId) MarshalJSON() ([]byte, error) { -// str := t.String() -// if str == "" { -// return nil, nil -// } -// return json.Marshal([]byte(base64.RawURLEncoding.EncodeToString(t[:]))) -//} +func (t TraceID) MarshalJSON() ([]byte, error) { + return json.Marshal(t.String()) +} + +func (t *TraceID) UnmarshalJSON(bb []byte) error { + var str string + if err := json.Unmarshal(bb, &str); err != nil { + return err + } + tt, err := TraceIDFromString(str) + if err != nil { + return err + } + *t = tt + return err +} + +// SpanID like in OpenTelemetry +type SpanID [8]byte + +const SpanIDSize = 8 + +func (s SpanID) IsZero() bool { + return s == SpanID{} +} + +func NewSpanID() (res SpanID) { + _, _ = rand.Read(res[:]) + return res +} + +// default string format for SpanID is base64! +func (s SpanID) String() string { + if s.IsZero() { + return "" + } + return base64.RawURLEncoding.EncodeToString(s[:]) +} + +func (s SpanID) Bytes() []byte { + return s[:] +} + +func (s SpanID) AsNullableBytes() []byte { + if s.IsZero() { + return nil + } + return s[:] +} + +func ParseSpanID(spanID string) (res SpanID, err error) { + bytes, err := base64.RawURLEncoding.DecodeString(spanID) + if err != nil { + return SpanID{}, err + } + if len(bytes) != SpanIDSize { + return SpanID{}, errors.Errorf("ParseSpanID: wrong SpanID size %v expected %v", len(bytes), SpanIDSize) + } + copy(res[:], bytes) + return res, err +} + +func (s SpanID) MarshalText() ([]byte, error) { + if s.IsZero() { + return nil, nil + } + return []byte(s.String()), nil +} + +func (s SpanID) MarshalJSON() ([]byte, error) { + return json.Marshal(s.String()) +} + +func (s *SpanID) UnmarshalJSON(bb []byte) error { + var str string + if err := json.Unmarshal(bb, &str); err != nil { + return err + } + tt, err := SpanIDFromString(str) + if err != nil { + return err + } + *s = tt + return err +} + +func SpanIDFromBytes(bb []byte) (res SpanID, err error) { + if len(bb) != SpanIDSize { + return SpanID{}, errors.Errorf("SpanIDFromBytes: wrong SpanID size %v expected %v", len(bb), SpanIDSize) + } + copy(res[:], bb) + return res, nil +} + +func SpanIDFromBytesOrZero(bb []byte) SpanID { + res, err := SpanIDFromBytes(bb) + if err != nil { + return SpanID{} + } + return res +} + +func SpanIDFromString(s string) (res SpanID, err error) { + bytes, err := base64.RawURLEncoding.DecodeString(s) + if err != nil { + return SpanID{}, err + } + if len(bytes) == 0 { + return SpanID{}, nil + } + if len(bytes) != SpanIDSize { + return SpanID{}, errors.Errorf("SpanIDFromString: wrong SpanID size %v expected %v", len(bytes), SpanIDSize) + } + copy(res[:], bytes) + return res, err +} diff --git a/traceid_test.go b/traceid_test.go new file mode 100644 index 0000000..1e18f54 --- /dev/null +++ b/traceid_test.go @@ -0,0 +1,94 @@ +package klogga + +import ( + "encoding/json" + "github.com/stretchr/testify/require" + "testing" +) + +func TestCreateParseTraceID(t *testing.T) { + id := NewTraceID() + + res, err := TraceIDFromString(id.String()) + require.NoError(t, err) + require.Equal(t, id, res) +} + +func TestJsonMarshalTraceID(t *testing.T) { + id := NewTraceID() + jsonStr, err := json.Marshal(&id) + require.NoError(t, err) + var unmarshalledID TraceID + err = json.Unmarshal(jsonStr, &unmarshalledID) + require.NoError(t, err) + require.Equal(t, id, unmarshalledID) +} + +func TestJsonMarshalTraceIDinStruct(t *testing.T) { + id := NewTraceID() + type St struct { + ID TraceID + Some string + } + val := St{id, "lala"} + + jsonStr, err := json.Marshal(&val) + require.NoError(t, err) + var unmarshalledID St + err = json.Unmarshal(jsonStr, &unmarshalledID) + require.NoError(t, err) + require.Equal(t, id, unmarshalledID.ID) + t.Log("json:", string(jsonStr)) +} + +func TestCreateParseSpanID(t *testing.T) { + id := NewSpanID() + + res, err := SpanIDFromString(id.String()) + require.NoError(t, err) + require.Equal(t, id, res) +} + +func TestJsonMarshalSpanID(t *testing.T) { + id := NewSpanID() + jsonStr, err := json.Marshal(&id) + require.NoError(t, err) + var unmarshalledID SpanID + err = json.Unmarshal(jsonStr, &unmarshalledID) + require.NoError(t, err) + require.Equal(t, id, unmarshalledID) +} + +func TestJsonMarshalSpanIDinStruct(t *testing.T) { + id := NewSpanID() + type St struct { + Some string + ID SpanID + } + val := St{"lala", id} + + jsonStr, err := json.Marshal(&val) + require.NoError(t, err) + var unmarshalledID St + err = json.Unmarshal(jsonStr, &unmarshalledID) + require.NoError(t, err) + require.Equal(t, id, unmarshalledID.ID) +} + +func TestJsonMarshalIDsInStruct(t *testing.T) { + idT, idS := TraceID{}, SpanID{} + type St struct { + ID SpanID + TraceID TraceID + } + val := St{idS, idT} + + jsonStr, err := json.Marshal(&val) + require.NoError(t, err) + var unmarshalledID St + err = json.Unmarshal(jsonStr, &unmarshalledID) + require.NoError(t, err) + require.Equal(t, idS, unmarshalledID.ID) + require.Equal(t, idT, unmarshalledID.TraceID) + t.Log("json:", string(jsonStr)) +} diff --git a/util/errs/errs.go b/util/errs/errs.go index 629a761..04396eb 100644 --- a/util/errs/errs.go +++ b/util/errs/errs.go @@ -1,7 +1,7 @@ package errs import ( - "github.com/hashicorp/go-multierror" + mr "github.com/hashicorp/go-multierror" ) // Append concatenates errors into one, creating a clean message if there is only one error @@ -18,5 +18,5 @@ func Append(err error, errs ...error) error { } else if err == nil && len(cleanErrs) == 1 { return cleanErrs[0] } - return multierror.Append(err, cleanErrs...) + return mr.Append(err, cleanErrs...) } diff --git a/util/reflectutil/reflectutil.go b/util/reflectutil/reflectutil.go index 6b08868..aa9aca2 100644 --- a/util/reflectutil/reflectutil.go +++ b/util/reflectutil/reflectutil.go @@ -6,7 +6,8 @@ import ( "strings" ) -// GetPackageClassFunc parses package (last entry in the path), class(==receiver type) and func for the call one level up +// GetPackageClassFunc parses package (last entry in the path), +// class(==receiver type) and func for the call one level up func GetPackageClassFunc() (string, string, string) { pc := make([]uintptr, 10) runtime.Callers(2, pc) diff --git a/util/stringutil/stringutil.go b/util/stringutil/stringutil.go index 3da97e2..6c73df6 100644 --- a/util/stringutil/stringutil.go +++ b/util/stringutil/stringutil.go @@ -16,8 +16,7 @@ func Default(str string, def string) string { } func MaxLen(str string, maxLen int) string { - r := []rune(str) - if len(r) > maxLen { + if r := []rune(str); len(r) > maxLen { return string(r[:maxLen]) } return str diff --git a/util/testutil/env.go b/util/testutil/env.go index 8094535..6dde212 100644 --- a/util/testutil/env.go +++ b/util/testutil/env.go @@ -6,9 +6,11 @@ import ( ) func IntegrationEnv(t *testing.T, name string) string { + t.Helper() result := os.Getenv(name) if result == "" { t.Skipf("%s environment variable is required to run this test", name) } + t.Logf("ENV %s=%s", name, result) return result } diff --git a/util/testutil/hostname.go b/util/testutil/hostname.go deleted file mode 100644 index 4d7fee9..0000000 --- a/util/testutil/hostname.go +++ /dev/null @@ -1,11 +0,0 @@ -package testutil - -type Hostname struct{} - -func NewHostname() Hostname { - return Hostname{} -} - -func (h Hostname) Name() (string, error) { - return "testmachine", nil -} diff --git a/util/testutil/timeout.go b/util/testutil/timeout.go index 39fd833..4b39341 100644 --- a/util/testutil/timeout.go +++ b/util/testutil/timeout.go @@ -6,7 +6,7 @@ import ( ) func Timeout() context.Context { - //nolint + //nolint:govet // cancel not needed in tests timeout, _ := context.WithTimeout(context.Background(), 5*time.Second) return timeout } diff --git a/utils.go b/utils.go index e1c0a5f..4b80791 100644 --- a/utils.go +++ b/utils.go @@ -12,22 +12,22 @@ import ( const TimestampLayout = "2006-01-02 15:04:05.000" -type WriterAdapter struct { +type WriterExporter struct { w io.Writer } -func NewWriterAdapter(writer io.Writer) *WriterAdapter { - return &WriterAdapter{w: writer} +func NewWriterExporter(writer io.Writer) *WriterExporter { + return &WriterExporter{w: writer} } -func (w WriterAdapter) Write(ctx context.Context, spans []*Span) error { +func (w WriterExporter) Write(ctx context.Context, spans []*Span) error { for _, span := range spans { - _, _ = w.w.Write([]byte(span.Stringify() + "\n")) + _, _ = w.w.Write([]byte(span.Stringify("\n"))) } return nil } -func (w WriterAdapter) Shutdown(context.Context) error { +func (w WriterExporter) Shutdown(context.Context) error { return nil } @@ -73,8 +73,9 @@ func RoundDur(d time.Duration) string { } } -// NewTestErrTracker wraps tracer to fail test on the span error +// NewTestErrTracker wraps tracer to fail test on the span error. func NewTestErrTracker(t *testing.T, trs Tracer) Tracer { + t.Helper() return errorTracker{ t: t, trs: trs, diff --git a/utils_test.go b/utils_test.go index 900dddc..1931aa3 100644 --- a/utils_test.go +++ b/utils_test.go @@ -2,8 +2,8 @@ package klogga import ( "context" + "github.com/KasperskyLab/klogga/util/testutil" "github.com/stretchr/testify/require" - "go.kl/klogga/util/testutil" "strings" "testing" ) @@ -11,7 +11,7 @@ import ( func TestWriterAdapter(t *testing.T) { span := StartLeaf(context.Background()).Tag("data", "334") sb := strings.Builder{} - rTrs := NewWriterAdapter(&sb) + rTrs := NewWriterExporter(&sb) defer func() { require.NoError(t, rTrs.Shutdown(testutil.Timeout())) }() span.FlushTo(NewFactory(rTrs).NamedPkg()) str := sb.String() From da3619dc579d4f3604dbb734caf69ff14a9582ae Mon Sep 17 00:00:00 2001 From: Danila Protsenko Date: Mon, 17 Oct 2022 18:36:03 +0300 Subject: [PATCH 2/2] klogga v0.3 --- AUTHORS.md | 1 + LICENSE.TXT | 4 +- README.md | 91 +++- adapters/fx/fx_adapter.go | 21 +- adapters/fx/runner.go | 26 +- adapters/grpc/grpc_adapter.go | 2 - batcher/batcher.go | 16 +- batcher/batcher_test.go | 39 ++ examples/basic_app/main.go | 5 +- examples/fx_app/main.go | 60 +++ exporter.go | 3 + exporters/otel/otel_exporter.go | 6 +- exporters/otel/readme.md | 1 + exporters/postgres/pg_exporter.go | 130 +++--- exporters/postgres/pg_exporter_test.go | 35 ++ .../postgres/pgconnector/pg_example_test.go | 4 + exporters/postgres/postgres_types.go | 22 +- exporters/postgres/table_schema.go | 18 +- exporters/readme.md | 1 + factory.go | 8 +- go.mod | 43 +- go.sum | 91 ++-- legal_notices.txt | 389 +++++++++++++----- log_levels.go | 3 + span.go | 151 +++---- span_defaults.go | 10 + span_fuzz_test.go | 18 + span_json.go | 42 ++ span_opt.go | 28 ++ span_test.go | 2 +- testdata/log.std.out.v5/logger.go | 21 + testdata/loggers.v4.0.1/logger.go | 21 + testdata/loggers.v4/logger.go | 21 + testdata/loggers/2.2.2/logger.go | 21 + testdata/loggers/logger.go | 21 + testdata/loggers/v2.1/logger.go | 21 + testdata/loggers/v2/logger.go | 21 + testdata/loggersv3/logger.go | 21 + testdata/nested/logger.go | 21 + testdata/nested/very/deep/logger.go | 21 + .../nested/very/deep/v4/black/hole/logger.go | 21 + .../deep/v4/black/hole/v6/loggers/logger.go | 21 + .../v4/black/hole/v6/loggers/v7/logger.go | 21 + testdata/nested/very/deep/v4/black/logger.go | 21 + testdata/nested/very/logger.go | 21 + testdata/package_test.go | 79 ++++ testdata/parent/child/child.go | 7 + testdata/parent/parent.go | 21 + testdata/testpkg/v2/span_test.go | 11 + testdata/testpkg/v2/testpkg.go | 11 + util/defaults.go | 10 + util/errs/errs_test.go | 3 +- util/reflectutil/reflectutil.go | 117 +++++- util/reflectutil/reflectutil_test.go | 37 +- util/testutil/timeout.go | 10 +- utils.go | 2 + 56 files changed, 1504 insertions(+), 389 deletions(-) create mode 100644 examples/fx_app/main.go create mode 100644 exporters/otel/readme.md create mode 100644 exporters/readme.md create mode 100644 span_defaults.go create mode 100644 span_fuzz_test.go create mode 100644 span_json.go create mode 100644 testdata/log.std.out.v5/logger.go create mode 100644 testdata/loggers.v4.0.1/logger.go create mode 100644 testdata/loggers.v4/logger.go create mode 100644 testdata/loggers/2.2.2/logger.go create mode 100644 testdata/loggers/logger.go create mode 100644 testdata/loggers/v2.1/logger.go create mode 100644 testdata/loggers/v2/logger.go create mode 100644 testdata/loggersv3/logger.go create mode 100644 testdata/nested/logger.go create mode 100644 testdata/nested/very/deep/logger.go create mode 100644 testdata/nested/very/deep/v4/black/hole/logger.go create mode 100644 testdata/nested/very/deep/v4/black/hole/v6/loggers/logger.go create mode 100644 testdata/nested/very/deep/v4/black/hole/v6/loggers/v7/logger.go create mode 100644 testdata/nested/very/deep/v4/black/logger.go create mode 100644 testdata/nested/very/logger.go create mode 100644 testdata/package_test.go create mode 100644 testdata/parent/child/child.go create mode 100644 testdata/parent/parent.go create mode 100644 testdata/testpkg/v2/span_test.go create mode 100644 testdata/testpkg/v2/testpkg.go create mode 100644 util/defaults.go diff --git a/AUTHORS.md b/AUTHORS.md index 6ba4018..92d0363 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -2,3 +2,4 @@ The following authors have created the source code of "klogga"
published and distributed by AO Kaspersky Lab as the owner:
Danila Protsenko
+Artem Doktor
diff --git a/LICENSE.TXT b/LICENSE.TXT index 6c17d08..c75ebc4 100644 --- a/LICENSE.TXT +++ b/LICENSE.TXT @@ -1,6 +1,8 @@ MIT License -Copyright (c) 2021 klogga contributors +Copyright (c) 2022 AO Kaspersky Lab +Copyright (c) 2022 klogga contributors +All Rights Reserved Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 979afd0..4ab254c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,91 @@ # Introduction Opinionated logging-audit-tracing library. -Data collected via klogga can be configured to be exported to different sources, including traditional text logs, but with emphasis on structured storages, primarily time-series databases and Open Telemetry. +Data collected via **klogga** can be configured to be exported to different sources, including traditional text logs, but with emphasis on structured storages, primarily time-series databases and Open Telemetry. + +The ideology is explained here (in russian) [Logging as fact-stream](https://www.youtube.com/watch?v=1lcKzy6j6-k) + +**klogga** is in development and as of yet does not even have a 1.0 release. +API is subject to changes, although we do not expect any. +It is used extensively in Kaspersky internal projects written in go. # Getting Started -import klogga -configure your root tracer, don't forget to use batcher for buffered traces -see examples/tracers_tree.go +## Basic +Import klogga, configure your root tracer, don't forget to use batcher for buffered traces +see [examples](examples/basic_app/main.go) + +```go +package main + +import ( + "context" + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/util" + "time" +) + +func main() { + // kreating a factory, with the simplest exporter + tf := util.DefaultFactory() + + // kreating a tracer with a package name + trs := tf.NamedPkg() + + // starting a span + // for now, we'll ignore context + span, _ := klogga.Start(context.Background()) + // span will be written on func exit + defer trs.Finish(span) + + // tag - potentially indexed + span.Tag("app", "hello-world") + // value - for metrics, or bigger values + span.Val("meaning", 42) + // sleep a bit, to have us some non-zero duration + time.Sleep(154 * time.Millisecond) +} +``` +OUTPUT: +``` +2022-02-03 04:05:06.789 I main [main.] main() (155.17ms); app:'hello-world'; meaning:'42'; id: 65C6p2Mq2N4; trace: 4nxwjs5WR9G9A-1-dw4cqA +``` + +## Extended + +**klogga** comes with [fx](https://github.com/uber-go/fx) integration.
+See [fx example](examples/fx_app/main.go) + + +# Features and ideas +This list will be covered and structured in the future. + +- logging basic information automatically without manual setup +- logging code information for easy search (uses reflection) +- tracing support: parent-child spans and trace-id +- opentelemetry support +- TODO go fuzz tests +- TODO trace information propagation support for HTTP, GRPC +- TODO transport support +- TODO postgres type mapping customization +- TODO span serialization to protobuf +- TODO elasticsearch exporter (if opentelemetry is not enough) +- TODO more docs for postgreSQL & timescaleDB integration +- TODO increase test coverage (~60% to ~80%) + + +# Running tests +## Unit test +Unit tests do not require any environment, all tests that require environment will be skipped +```shell +go test ./... +``` + + +## Integration tests +Postgresql (timescaleDB) tests can be run on any empty database. +Tests use environment variable KLOGGA_PG_CONNECTION_STRING to connect to PG. +Use [docker-compose.yml](docker-compose.yml) to start default instance. +Default setup: +```shell +podman-compose up -d +KLOGGA_PG_CONNECTION_STRING='user=postgres password=postgres sslmode=disable' go test ./... +``` diff --git a/adapters/fx/fx_adapter.go b/adapters/fx/fx_adapter.go index 347e500..044d32b 100644 --- a/adapters/fx/fx_adapter.go +++ b/adapters/fx/fx_adapter.go @@ -22,18 +22,18 @@ func (t fxTracer) LogEvent(event fxevent.Event) { span.Tag("event", reflect.TypeOf(event).String()) switch e := event.(type) { case *fxevent.OnStartExecuting: - span.OverrideName(e.CallerName).Tag("callee", e.FunctionName) + span.OverrideName("OnStartExecuting").Tag("caller", e.CallerName).Tag("callee", e.FunctionName) case *fxevent.OnStartExecuted: - span.OverrideName(e.CallerName).Tag("callee", e.FunctionName) + span.OverrideName("OnStartExecuted").Tag("caller", e.CallerName).Tag("callee", e.FunctionName) if e.Err != nil { span.ErrVoid(e.Err) } else { span.Message("runtime:" + e.Runtime.String()) } case *fxevent.OnStopExecuting: - span.OverrideName(e.CallerName).Tag("callee", e.FunctionName) + span.OverrideName("OnStopExecuting").Tag("caller", e.CallerName).Tag("callee", e.FunctionName) case *fxevent.OnStopExecuted: - span.OverrideName(e.CallerName).Tag("callee", e.FunctionName) + span.OverrideName("OnStopExecuted").Tag("caller", e.CallerName).Tag("callee", e.FunctionName) if e.Err != nil { span.ErrVoid(e.Err) } else { @@ -45,9 +45,9 @@ func (t fxTracer) LogEvent(event fxevent.Event) { span.OverrideName("Provided"). Message("output types:" + strings.Join(e.OutputTypeNames, ",")).ErrVoid(e.Err) case *fxevent.Invoking: - span.OverrideName(e.FunctionName) + span.OverrideName("Invoking").Tag("func", e.FunctionName) case *fxevent.Invoked: - span.OverrideName(e.FunctionName) + span.OverrideName("Invoked").Tag("func", e.FunctionName) if e.Err != nil { span.ErrSpan(e.Err).Message("stack:" + e.Trace) } @@ -62,7 +62,7 @@ func (t fxTracer) LogEvent(event fxevent.Event) { case *fxevent.Started: span.OverrideName("Started").ErrVoid(e.Err) case *fxevent.LoggerInitialized: - span.OverrideName(e.ConstructorName).ErrVoid(e.Err) + span.OverrideName("LoggerInitialized").Tag("caller", e.ConstructorName).ErrVoid(e.Err) } } @@ -80,12 +80,15 @@ func Module(tf klogga.TracerProvider) fx.Option { // Full Set up the default logging for the app // registering logging and the klogga factory, -// that later can be reconfigured with more loggers +// that later can be reconfigured with more loggers via fx.Decorate func Full() fx.Option { tf := klogga.NewFactory(golog.New(nil)) return fx.Options( Module(tf), fx.Supply(tf), fx.Provide(func(tf *klogga.Factory) klogga.TracerProvider { return tf }), - ) + fx.Invoke(func(tf *klogga.Factory, lc fx.Lifecycle) error { + lc.Append(fx.Hook{OnStop: tf.Shutdown}) + return nil + })) } diff --git a/adapters/fx/runner.go b/adapters/fx/runner.go index 5e1db34..3c198e3 100644 --- a/adapters/fx/runner.go +++ b/adapters/fx/runner.go @@ -2,6 +2,7 @@ package fx import ( "context" + "errors" "fmt" "github.com/KasperskyLab/klogga" "go.uber.org/fx" @@ -56,20 +57,22 @@ func (rr RunnersGroup) RegisterWithErrors(tf klogga.TracerProvider, lc fx.Lifecy ctx, cancelFunc := context.WithCancel(context.Background()) lc.Append(ToHookWithCtx(runner, cancelFunc)) go func(r Runner) { + runnerName := reflect.TypeOf(r).String() select { case err := <-re.Error(): + if err == nil { + err = errors.New("") + } klogga.Message("runner error"). - Tag("runner", reflect.TypeOf(r).String()). - ErrSpan(err).FlushTo(trs) - case <-ctx.Done(): - - } - - if err := s.Shutdown(); err != nil { - klogga.Message("shutdowner error"). - Tag("runner", reflect.TypeOf(r).String()). + Tag("runner", runnerName). ErrSpan(err).FlushTo(trs) + if err := s.Shutdown(); err != nil { + klogga.Message("shutdowner error"). + Tag("runner", runnerName). + ErrSpan(err).FlushTo(trs) + } + case <-ctx.Done(): } }(runner) } @@ -86,9 +89,8 @@ func ToHookWithCtx(r Runner, onStopped context.CancelFunc) fx.Hook { return fx.Hook{ OnStart: r.Start, OnStop: func(ctx context.Context) error { - err := r.Stop(ctx) - onStopped() - return err + defer onStopped() + return r.Stop(ctx) }, } } diff --git a/adapters/grpc/grpc_adapter.go b/adapters/grpc/grpc_adapter.go index 4bfb188..77a4d6f 100644 --- a/adapters/grpc/grpc_adapter.go +++ b/adapters/grpc/grpc_adapter.go @@ -12,8 +12,6 @@ type LoggerV2 struct { } type Conf struct { - // 0 - log everything - // 4 - log nothing VerbosityLevel klogga.LogLevel } diff --git a/batcher/batcher.go b/batcher/batcher.go index e5dd11e..f3cdb12 100644 --- a/batcher/batcher.go +++ b/batcher/batcher.go @@ -44,10 +44,16 @@ func (c *Config) GetBufferSize() int { return c.GetBatchSize() * 5 } -func ConfigDefault() Config { +// ConfigDefault generates default batcher config +// non-nil optional config will be used instead +func ConfigDefault(cc ...*Config) Config { + if len(cc) > 0 && cc[0] != nil { + return *cc[0] + } return Config{ - BatchSize: 20, - Timeout: 2 * time.Second, + BatchSize: 512, + BufferSize: 2048, + Timeout: 5 * time.Second, } } @@ -132,8 +138,12 @@ func (b *Batcher) Write(ctx context.Context, spans []*klogga.Span) error { func (b *Batcher) Shutdown(ctx context.Context) (err error) { b.TriggerFlush() + if b.stop == nil { + return nil + } select { case b.stop <- struct{}{}: + b.stop = nil case <-ctx.Done(): err = ctx.Err() } diff --git a/batcher/batcher_test.go b/batcher/batcher_test.go index 3462a08..4f2e2ec 100644 --- a/batcher/batcher_test.go +++ b/batcher/batcher_test.go @@ -4,6 +4,7 @@ import ( "github.com/KasperskyLab/klogga" "github.com/KasperskyLab/klogga/util/testutil" "github.com/stretchr/testify/require" + "sync" "testing" "time" ) @@ -146,3 +147,41 @@ func TestWriteLotsOfSpans(t *testing.T) { time.Sleep(150 * time.Millisecond) require.True(t, len(tw.GetSpans()) >= 10) } + +func TestShutdownTwice(t *testing.T) { + tw := &exporterStub{} + rawTracer := New( + tw, Config{ + BatchSize: 1000, + Timeout: 100 * time.Millisecond, + }, + ) + tf := klogga.NewFactory(rawTracer) + + err := tf.Shutdown(testutil.Timeout()) + require.NoError(t, err) + err = tf.Shutdown(testutil.Timeout()) + require.NoError(t, err) +} + +func TestShutdownManyGoroutines(t *testing.T) { + tw := &exporterStub{} + rawTracer := New( + tw, Config{ + BatchSize: 1000, + Timeout: 100 * time.Millisecond, + }, + ) + tf := klogga.NewFactory(rawTracer) + + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + go func() { + wg.Add(1) + defer wg.Done() + err := tf.Shutdown(testutil.Timeout()) + require.NoError(t, err) + }() + } + wg.Wait() +} diff --git a/examples/basic_app/main.go b/examples/basic_app/main.go index d9ad44f..76a5564 100644 --- a/examples/basic_app/main.go +++ b/examples/basic_app/main.go @@ -3,13 +3,13 @@ package main import ( "context" "github.com/KasperskyLab/klogga" - "github.com/KasperskyLab/klogga/exporters/golog" + "github.com/KasperskyLab/klogga/util" "time" ) func main() { // kreating a factory, with the simplest exporter - tf := klogga.NewFactory(golog.New(nil)) + tf := util.DefaultFactory() // kreating a tracer with a package name trs := tf.NamedPkg() @@ -26,5 +26,4 @@ func main() { span.Val("meaning", 42) // sleep a bit, to have us some non-zero duration time.Sleep(154 * time.Millisecond) - } diff --git a/examples/fx_app/main.go b/examples/fx_app/main.go new file mode 100644 index 0000000..830ad35 --- /dev/null +++ b/examples/fx_app/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "context" + "github.com/KasperskyLab/klogga" + fxAdapter "github.com/KasperskyLab/klogga/adapters/fx" + "go.uber.org/fx" + "time" +) + +func main() { + fx.New(CreateApp()).Run() +} + +func CreateApp() fx.Option { + return fx.Options( + fx.Provide(NewRunner, + fx.Annotate(func(r *Runner) fxAdapter.Runner { return r }, fxAdapter.TagRunner...)), + fxAdapter.Full(), + fx.Invoke(fxAdapter.RegisterRunners), // register runnerA + ) +} + +type Runner struct { + trs klogga.Tracer + stop chan struct{} +} + +func NewRunner(trsFactory klogga.TracerProvider) *Runner { + return &Runner{ + trs: trsFactory.Named("runner_a"), + } +} + +func (r *Runner) Start(ctx context.Context) error { + span := klogga.StartLeaf(ctx) + defer r.trs.Finish(span) + + go func() { + for i := 0; ; i++ { + r.LogSomething(i) + select { + case <-r.stop: + return + case <-time.After(2 * time.Second): + } + } + }() + return nil +} + +func (*Runner) Stop(context.Context) error { + return nil +} + +func (r *Runner) LogSomething(iteration int) { + span := klogga.StartLeaf(context.Background(), klogga.WithName("run_func")) + defer r.trs.Finish(span) + span.Val("iteration", iteration) +} diff --git a/exporter.go b/exporter.go index 3b8c498..f20e2af 100644 --- a/exporter.go +++ b/exporter.go @@ -9,6 +9,9 @@ import ( // to be more generic accepts batches right away type Exporter interface { Write(ctx context.Context, spans []*Span) error + + // Shutdown is called to cleanup the exporter. Exporter cannot be used after that. + // Should be idempotent i.e. should work fine when called multiple times. Shutdown(ctx context.Context) error } diff --git a/exporters/otel/otel_exporter.go b/exporters/otel/otel_exporter.go index 6c6b933..3f32489 100644 --- a/exporters/otel/otel_exporter.go +++ b/exporters/otel/otel_exporter.go @@ -7,7 +7,6 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" - "time" ) // Exporter basic exporter of klogga spans to otel tracer @@ -38,7 +37,7 @@ func (t *Exporter) Write(ctx context.Context, spans []*klogga.Span) error { } _, otelSpan := t.tracer.Start( trace.ContextWithSpanContext(ctx, trace.NewSpanContext(config)), - span.Name(), + span.Component().String()+"/"+span.PackageClass()+"."+span.Name(), trace.WithTimestamp(span.StartedTs()), ) @@ -68,8 +67,7 @@ func (t *Exporter) Write(ctx context.Context, spans []*klogga.Span) error { // possibly should have something like this: // otelSpan.SetAttributes(attribute.String("klogga_id", span.ID().String())) - time.Sleep(500 * time.Millisecond) - otelSpan.End() + otelSpan.End(trace.WithTimestamp(span.FinishedTs())) } return nil } diff --git a/exporters/otel/readme.md b/exporters/otel/readme.md new file mode 100644 index 0000000..05367c9 --- /dev/null +++ b/exporters/otel/readme.md @@ -0,0 +1 @@ +Exporter [to opentelemetry go instrumentation](https://opentelemetry.io/docs/instrumentation/go/) \ No newline at end of file diff --git a/exporters/postgres/pg_exporter.go b/exporters/postgres/pg_exporter.go index 2e3c405..6af3967 100644 --- a/exporters/postgres/pg_exporter.go +++ b/exporters/postgres/pg_exporter.go @@ -24,15 +24,15 @@ const ( DefaultSchema = "audit" ErrorPostgresTable = DefaultSchema + "." + ErrorPostgresTableName - defaultWriteTimeout = time.Second - msgUnableToConnectPG = "unable to connect PG" - msgUnableToBeginTx = "unable to begin TX" - msgUnableToCommitTx = "unable to commit TX" - msgUnableToPrepareStatement = "unable to prepare statement" - msgUnableToExecStatement = "unable to exec statement" - msgUnableToQuery = "unable to query" - magUnableToScan = "unable to scan" - msgUnableToExec = "unable to exec" + defaultWriteTimeout = time.Second + + messageUnableToConnectPG = "unable to connect to postgres" + messageUnableToBeginTx = "unable to begin transaction" + messageUnableToCommitTx = "unable to commit transaction" + messageUnableToPrepare = "unable to prepare statement" + messageUnableToQuery = "unable to query" + messageUnableToScan = "unable to scan" + messageUnableToExec = "unable to exec" ) // Conf parameters on how klogga works with DB @@ -71,7 +71,7 @@ type Exporter struct { // New to be used with batcher // cfg - config // connFactory - connection to PG -// trs - a tracer to write pg_exporter logs, like DB changes, pass nil to setup default golog tracer +// trs - a tracer to write pg_exporter logs, like DB changes, pass nil to set default golog tracer func New(cfg *Conf, connFactory Connector, trs klogga.Tracer) *Exporter { if trs == nil { trs = klogga.NewFactory(golog.New(nil)).NamedPkg() @@ -167,8 +167,11 @@ func (e *Exporter) Write(ctx context.Context, spans []*klogga.Span) error { }, ) - recordSets, errSpans := e.createRecordSets(spans...) - e.writeErrSpans(ctx, errSpans) + recordSets, errDescriptors := e.createRecordSets(spans...) + for i := 0; i < len(errDescriptors); i++ { + e.writeErr(ctx, errDescriptors[i].Span) + } + for tableName, recordSet := range recordSets { if !e.cfg.SkipSchemaCreation { err := e.updateSchema(ctx, tableName, recordSet) @@ -203,7 +206,9 @@ func (e *Exporter) updateSchema(ctx context.Context, tableName string, dataset R } else { alterSchema, failures := schema.GetAlterSchema(dataset) if len(failures) > 0 { - e.writeErrSpans(ctx, failures) + for i := 0; i < len(failures); i++ { + e.writeErr(ctx, failures[i].Span) + } return span.Err( errors.Errorf( "alter table failed (%v failures), first failure: %v", len(failures), failures[0].Err(), @@ -232,7 +237,7 @@ func (e *Exporter) writeRecordSet(ctx context.Context, tableName string, recordS conn, err := e.connFactory.GetConnection(ctx) if err != nil { - span.ErrVoid(errors.Wrap(err, msgUnableToConnectPG)) + span.ErrVoid(errors.Wrap(err, messageUnableToConnectPG)) return } defer func() { span.DeferErr(conn.Close()) }() @@ -240,7 +245,7 @@ func (e *Exporter) writeRecordSet(ctx context.Context, tableName string, recordS isCommitted := false tx, err := conn.BeginTx(ctx, nil) if err != nil { - span.ErrVoid(errors.Wrap(err, msgUnableToBeginTx)) + span.ErrVoid(errors.Wrap(err, messageUnableToBeginTx)) return } defer func() { @@ -254,7 +259,7 @@ func (e *Exporter) writeRecordSet(ctx context.Context, tableName string, recordS span.Val("columns_count", recordSet.Schema.ColumnsCount()) stmt, err := tx.Prepare(query) if err != nil { - span.ErrVoid(errors.Wrap(err, msgUnableToPrepareStatement)) + span.ErrVoid(errors.Wrap(err, messageUnableToPrepare)) return } defer func() { @@ -271,8 +276,8 @@ func (e *Exporter) writeRecordSet(ctx context.Context, tableName string, recordS strWarn = sErr.Error() } - vv := []interface{}{ - span.StartedTs(), + vv := []any{ + span.StartedTs().UTC(), span.ID().Bytes(), span.TraceID().AsUUID(), span.Host(), @@ -295,7 +300,7 @@ func (e *Exporter) writeRecordSet(ctx context.Context, tableName string, recordS } if _, err := stmt.ExecContext(ctx, vv...); err != nil { - span.ErrVoid(errors.Wrap(err, msgUnableToExecStatement)) + span.ErrVoid(errors.Wrap(err, messageUnableToExec)) continue } } @@ -306,7 +311,7 @@ func (e *Exporter) writeRecordSet(ctx context.Context, tableName string, recordS } if err := tx.Commit(); err != nil { - span.ErrVoid(errors.Wrap(err, msgUnableToCommitTx)) + span.ErrVoid(errors.Wrap(err, messageUnableToCommitTx)) } isCommitted = true } @@ -324,12 +329,12 @@ func (e *Exporter) createTable(ctx context.Context, tableName string, schema *Ta conn, err := e.connFactory.GetConnection(ctx) if err != nil { - return span.Err(errors.Wrap(err, msgUnableToConnectPG)) + return span.Err(errors.Wrap(err, messageUnableToConnectPG)) } defer func() { span.DeferErr(conn.Close()) }() if _, err := conn.ExecContext(ctx, q); err != nil { span.Val(vals.Query, q) - return span.Err(errors.Wrap(err, msgUnableToExec)) + return span.Err(errors.Wrap(err, messageUnableToExec)) } return nil } @@ -349,11 +354,11 @@ func (e *Exporter) alterTable(ctx context.Context, tableName string, schema *Tab conn, err := e.connFactory.GetConnection(ctx) if err != nil { - return span.Err(errors.Wrap(err, msgUnableToConnectPG)) + return span.Err(errors.Wrap(err, messageUnableToConnectPG)) } defer func() { span.DeferErr(conn.Close()) }() if _, err := conn.ExecContext(ctx, q); err != nil { - return span.Err(errors.Wrap(err, msgUnableToExec)) + return span.Err(errors.Wrap(err, messageUnableToExec)) } return nil } @@ -367,7 +372,7 @@ func (e *Exporter) loadSchemas(ctx context.Context) error { defer cancel() conn, err := e.connFactory.GetConnection(ctx) if err != nil { - return span.Err(errors.Wrap(err, msgUnableToConnectPG)) + return span.Err(errors.Wrap(err, messageUnableToConnectPG)) } defer func() { span.DeferErr(conn.Close()) }() @@ -380,10 +385,10 @@ func (e *Exporter) loadSchemas(ctx context.Context) error { rows, err := conn.QueryContext(ctx, query, args...) if err != nil { - return span.Err(errors.Wrap(err, msgUnableToQuery)) + return span.Err(errors.Wrap(err, messageUnableToQuery)) } if err := rows.Err(); err != nil { - return span.Err(errors.Wrap(err, msgUnableToQuery)) + return span.Err(errors.Wrap(err, messageUnableToQuery)) } defer func() { span.DeferErr(rows.Close()) @@ -394,7 +399,7 @@ func (e *Exporter) loadSchemas(ctx context.Context) error { var tableName string var colSchema ColumnSchema if err := rows.Scan(&tableName, &colSchema.Name, &colSchema.DataType); err != nil { - return span.Err(errors.Wrap(err, magUnableToScan)) + return span.Err(errors.Wrap(err, messageUnableToScan)) } if _, found := tables[tableName]; !found { tables[tableName] = NewTableSchema([]*ColumnSchema{}) @@ -419,47 +424,45 @@ func (e *Exporter) loadSchemas(ctx context.Context) error { return nil } -func (e *Exporter) writeErrSpans(ctx context.Context, errDescrs []ErrDescriptor) { - if len(errDescrs) == 0 { - return - } +func (e *Exporter) writeErr(ctx context.Context, errSpan *klogga.Span) { span, ctx := klogga.Start(ctx) defer e.writeIfErr(span) - span.Val(vals.Count, len(errDescrs)) conn, err := e.connFactory.GetConnection(ctx) if err != nil { - span.ErrVoid(errors.Wrap(err, msgUnableToConnectPG)) + span.ErrVoid(errors.Wrap(err, messageUnableToConnectPG)) return } defer func() { span.DeferErr(conn.Close()) }() statementText := e.errTable.InsertStatement(e.cfg.SchemaName, ErrorPostgresTableName) - for i := 0; i < len(errDescrs); i++ { - errDescr := errDescrs[i] - warn := "" - if errDescr.Warn() != nil { - warn = errDescr.Warn().Error() - } - _, err := conn.ExecContext( - ctx, - statementText, - errDescr.Span.StartedTs(), - errDescr.Span.ID().Bytes(), - errDescr.Span.TraceID().AsUUID(), - errDescr.Span.Host(), - errDescr.Span.PackageClass(), - errDescr.Span.Name(), - errDescr.Span.ParentID().AsNullableBytes(), - errDescr.Err().Error(), - warn, - errDescr.Span.Duration(), - errDescr.Span.Component(), - ) - if err != nil { - span.Val(vals.Query, statementText).ErrVoid(errors.Wrap(err, "unable to write bad span")) - } + warnText := "" + if errSpan.HasWarn() { + warnText = errSpan.Warns().Error() + } + errText := "" + if errSpan.HasErr() { + errText = errSpan.Errs().Error() + } + + _, err = conn.ExecContext( + ctx, + statementText, + errSpan.StartedTs().UTC(), + errSpan.ID().Bytes(), + errSpan.TraceID().AsUUID(), + errSpan.Host(), + errSpan.PackageClass(), + errSpan.Name(), + errSpan.ParentID().AsNullableBytes(), + errText, + warnText, + errSpan.Duration(), + errSpan.Component(), + ) + if err != nil { + span.Val(vals.Query, statementText).ErrVoid(errors.Wrap(err, "unable to write bad span")) } } @@ -488,18 +491,18 @@ func (e *Exporter) createRecordSets(spans ...*klogga.Span) (map[string]RecordSet for name, val := range e.getSpanVals(span) { name = toPgColumnName(name) - col, found := dataset.Schema.Column(name) + existingCol, found := dataset.Schema.Column(name) valType, _ := GetPgTypeVal(val) - newColumn := ColumnSchema{ + newCol := ColumnSchema{ Name: name, DataType: valType, } if !found { - dataset.Schema.AddColumn(newColumn) + dataset.Schema.AddColumn(newCol) continue } - if !strings.EqualFold(newColumn.DataType, col.DataType) { - errSpans = append(errSpans, newErrDescriptor(tableName, span, newColumn, *col)) + if existingCol.DataType != valType { + errSpans = append(errSpans, newErrDescriptor("", span, newCol, *existingCol)) } } @@ -545,5 +548,6 @@ func (e *Exporter) getSpanVals(span *klogga.Span) map[string]interface{} { func (e *Exporter) writeIfErr(span *klogga.Span) { if span.HasErr() { span.FlushTo(e.trs) + e.writeErr(context.Background(), span) } } diff --git a/exporters/postgres/pg_exporter_test.go b/exporters/postgres/pg_exporter_test.go index 3301cbe..84f7b63 100644 --- a/exporters/postgres/pg_exporter_test.go +++ b/exporters/postgres/pg_exporter_test.go @@ -93,3 +93,38 @@ func TestPgValueJsonEmpty(t *testing.T) { require.Equal(t, PgJsonbTypeName, pgt) require.Equal(t, "null", v) } + +type StringBased string + +func TestPgValueStringBased(t *testing.T) { + vv := "lalala" + pgt, v := GetPgTypeVal(StringBased(vv)) + require.Equal(t, PgTextTypeName, pgt) + require.Equal(t, v, vv) +} + +type structType struct { + V1 int + V2 int +} + +func TestStructType(t *testing.T) { + pgt, _ := GetPgTypeVal(structType{ + V1: 11, + V2: 12, + }) + require.Equal(t, PgJsonbTypeName, pgt) +} + +func TestStructPointerType(t *testing.T) { + pgt, _ := GetPgTypeVal(&structType{ + V1: 11, + V2: 12, + }) + require.Equal(t, PgJsonbTypeName, pgt) +} + +func TestArrayType(t *testing.T) { + pgt, _ := GetPgTypeVal([]int{2, 3, 4}) + require.Equal(t, PgJsonbTypeName, pgt) +} diff --git a/exporters/postgres/pgconnector/pg_example_test.go b/exporters/postgres/pgconnector/pg_example_test.go index faf8e17..d88399e 100644 --- a/exporters/postgres/pgconnector/pg_example_test.go +++ b/exporters/postgres/pgconnector/pg_example_test.go @@ -22,6 +22,10 @@ func TestConnectPg(t *testing.T) { _, err = rawConn.Exec("create schema if not exists audit") require.NoError(t, err) + // truncate audit.example_test table to ensure clean test + // ignore errors + _, _ = rawConn.Exec("TRUNCATE audit.example_test") + // postgres exporter needs a separate tracer, to write its own errors // this tracer shod use some simpler exporter, like golog, anything closer than external database // be careful not to recurse that ) diff --git a/exporters/postgres/postgres_types.go b/exporters/postgres/postgres_types.go index 62fed95..eee64bf 100644 --- a/exporters/postgres/postgres_types.go +++ b/exporters/postgres/postgres_types.go @@ -42,17 +42,27 @@ func GetPgTypeVal(a interface{}) (string, interface{}) { case fmt.Stringer: return PgTextTypeName, v.String() default: - if v != reflect.Struct { + t := reflect.TypeOf(v) + if isJsonDesired(t) || t.Kind() == reflect.Pointer && isJsonDesired(t.Elem()) { data, err := json.Marshal(v) - if err != nil { - return PgTextTypeName, fmt.Sprintf("%v", v) + if err == nil { + return PgJsonbTypeName, data } - return PgJsonbTypeName, data } return PgTextTypeName, fmt.Sprintf("%v", v) } } +func isJsonDesired(t reflect.Type) bool { + if t == nil { + return false + } + return t.Kind() == reflect.Struct || + t.Kind() == reflect.Slice || + t.Kind() == reflect.Map || + t.Kind() == reflect.Array +} + // finds column value by name in tags of vals of the span func findColumnValue(span *klogga.Span, name string) interface{} { val, found := span.Tags()[name] @@ -75,12 +85,14 @@ type ErrDescriptor struct { } func newErrDescriptor(table string, span *klogga.Span, col, existingCol ColumnSchema) ErrDescriptor { - return ErrDescriptor{ + descriptor := ErrDescriptor{ Table: table, Span: span, Column: col, ExistingColumn: existingCol, } + span.ErrVoid(descriptor.Err()) + return descriptor } func (e *ErrDescriptor) Err() error { diff --git a/exporters/postgres/table_schema.go b/exporters/postgres/table_schema.go index e42c5b3..79df15f 100644 --- a/exporters/postgres/table_schema.go +++ b/exporters/postgres/table_schema.go @@ -26,17 +26,17 @@ func NewTableSchema(columns []*ColumnSchema) *TableSchema { } } -func (t TableSchema) ColumnsCount() int { +func (t *TableSchema) ColumnsCount() int { return len(t.columns) } -func (t TableSchema) ColumnNames() []string { +func (t *TableSchema) ColumnNames() []string { return t.columnsNames } // GetAlterSchema returns missing columns' schema // returns spans that cannot be written just by adding columns -func (t TableSchema) GetAlterSchema(dataset RecordSet) (*TableSchema, []ErrDescriptor) { +func (t *TableSchema) GetAlterSchema(dataset RecordSet) (*TableSchema, []ErrDescriptor) { alterSchema := NewTableSchema([]*ColumnSchema{}) errDescriptors := make([]ErrDescriptor, 0) @@ -72,7 +72,7 @@ func (t *TableSchema) Merge(newCols []*ColumnSchema) *TableSchema { return tCopy } -func (t TableSchema) Column(name string) (*ColumnSchema, bool) { +func (t *TableSchema) Column(name string) (*ColumnSchema, bool) { col, ok := t.mm[name] return col, ok } @@ -85,12 +85,12 @@ func (t *TableSchema) AddColumn(col ColumnSchema) { t.mm[col.Name] = &col } -func (t TableSchema) Columns() []*ColumnSchema { +func (t *TableSchema) Columns() []*ColumnSchema { return t.columns } // InsertStatement NOT injection safe -func (t TableSchema) InsertStatement(schema string, tableName string) string { +func (t *TableSchema) InsertStatement(schema string, tableName string) string { paramsStr := "$1" for i := 2; i <= t.ColumnsCount(); i++ { paramsStr += fmt.Sprintf(",$%v", i) @@ -104,7 +104,7 @@ func (t TableSchema) InsertStatement(schema string, tableName string) string { } // CreateTableStatement NOT injection safe -func (t TableSchema) CreateTableStatement(schema, tableName string, timeCol *ColumnSchema, useTimescale bool) string { +func (t *TableSchema) CreateTableStatement(schema, tableName string, timeCol *ColumnSchema, useTimescale bool) string { b := strings.Builder{} b.WriteString(fmt.Sprintf("CREATE TABLE %s.%s\n", schema, tableName)) @@ -126,7 +126,7 @@ func (t TableSchema) CreateTableStatement(schema, tableName string, timeCol *Col } // AlterTableStatement NOT injection safe -func (t TableSchema) AlterTableStatement(schema, tableName string) string { +func (t *TableSchema) AlterTableStatement(schema, tableName string) string { b := strings.Builder{} b.WriteString(fmt.Sprintf("ALTER TABLE %v.%v\n", schema, tableName)) @@ -152,7 +152,7 @@ type ColumnSchema struct { IsNullable bool } -func (s ColumnSchema) SQL() string { +func (s *ColumnSchema) SQL() string { nullableStr := "null" if !s.IsNullable { nullableStr = "not " + nullableStr diff --git a/exporters/readme.md b/exporters/readme.md new file mode 100644 index 0000000..c3bc6f5 --- /dev/null +++ b/exporters/readme.md @@ -0,0 +1 @@ +This directory contains klogga exporters. \ No newline at end of file diff --git a/factory.go b/factory.go index 700a91a..ae27591 100644 --- a/factory.go +++ b/factory.go @@ -35,7 +35,7 @@ func (tf *Factory) Named(componentName ComponentName) Tracer { // NamedPkg creates a named tracer with the name as package name, where this constructor is called func (tf *Factory) NamedPkg() Tracer { - p, _, _ := reflectutil.GetPackageClassFunc() + p, _, _ := reflectutil.GetPackageClassFunc(2) return tf.Named(ComponentName(p)) } @@ -52,6 +52,12 @@ func (tf *Factory) AddExporter(exporter Exporter) *Factory { return tf } +// RemoveAllExporters clear exporters list, nothing will be exported +func (tf *Factory) RemoveAllExporters() *Factory { + tf.exporters = []Exporter{} + return tf +} + func (tf *Factory) write(ctx context.Context, spans []*Span) error { return tf.exporters.Write(ctx, spans) } diff --git a/go.mod b/go.mod index 92e2c07..57b2bd9 100644 --- a/go.mod +++ b/go.mod @@ -1,32 +1,49 @@ module github.com/KasperskyLab/klogga -go 1.16 +go 1.18 require ( - github.com/Masterminds/squirrel v1.5.2 + github.com/Masterminds/squirrel v1.5.3 github.com/golang/mock v1.6.0 github.com/google/uuid v1.3.0 github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/go-version v1.6.0 github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab - github.com/jmoiron/sqlx v1.3.4 + github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.4 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.12.1 - github.com/stretchr/testify v1.7.0 - go.opentelemetry.io/otel v1.3.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.3.0 - go.opentelemetry.io/otel/sdk v1.3.0 - go.opentelemetry.io/otel/trace v1.3.0 - go.uber.org/atomic v1.9.0 - go.uber.org/fx v1.16.0 + github.com/stretchr/testify v1.8.0 + go.opentelemetry.io/otel v1.10.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.10.0 + go.opentelemetry.io/otel/sdk v1.10.0 + go.opentelemetry.io/otel/trace v1.10.0 + go.uber.org/atomic v1.10.0 + go.uber.org/fx v1.18.1 ) require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/kr/text v0.2.0 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - go.uber.org/dig v1.13.0 // indirect - go.uber.org/multierr v1.7.0 // indirect - go.uber.org/zap v1.20.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + go.uber.org/dig v1.15.0 // indirect + go.uber.org/multierr v1.8.0 // indirect + go.uber.org/zap v1.23.0 // indirect + golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + google.golang.org/protobuf v1.26.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index daf390e..e347a3f 100644 --- a/go.sum +++ b/go.sum @@ -33,16 +33,14 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE= -github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= +github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -73,13 +71,13 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= -github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= -github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -123,8 +121,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -145,13 +142,15 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= -github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -224,11 +223,14 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -238,33 +240,26 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y= -go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.3.0 h1:Kte45gGM12Ks0pZng7Pi+IFlbbeY287ZpGX0s0G9al8= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.3.0/go.mod h1:PQLM+xJ3EMSZU9rMevmw+4nH1efyp23CW/nD9BlB3sg= -go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI= -go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= -go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY= -go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= +go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.10.0 h1:c9UtMu/qnbLlVwTwt+ABrURrioEruapIslTDYZHJe2w= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.10.0/go.mod h1:h3Lrh9t3Dnqp3NPwAZx7i37UFX7xrfnO1D+fuClREOA= +go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= +go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= +go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= +go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/dig v1.12.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw= -go.uber.org/dig v1.13.0 h1:bb9lVW3gtpQsNb07d0xL5vFwsjHidPJxaR/zSsbmfVQ= -go.uber.org/dig v1.13.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw= -go.uber.org/fx v1.16.0 h1:N8i80+X1DCX+qMRiKzM+jPPZiIiyK/bVCysga3+B+1w= -go.uber.org/fx v1.16.0/go.mod h1:OMoT5BnXcOaiexlpjtpE4vcAmzyDKyRs9TRYXCzamx8= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= +go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= +go.uber.org/fx v1.18.1 h1:I7VWkdv4iKcbpH7KVSi9Fe1LGmpJv+pbBIb9NidPb+E= +go.uber.org/fx v1.18.1/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= -go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -go.uber.org/zap v1.20.0 h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc= -go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -292,7 +287,6 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -384,10 +378,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -415,9 +407,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191030062658-86caa796c7ab/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -444,12 +433,9 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -538,13 +524,12 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/legal_notices.txt b/legal_notices.txt index 06fcd44..301e002 100644 --- a/legal_notices.txt +++ b/legal_notices.txt @@ -3,156 +3,141 @@ ------- This file contains information about dependencies which are used in the Klogga. - -atomic v1.9.0 -Copyright (c) 2016 Uber Technologies, Inc. +squirrel 1.5.3 +Copyright (C) 2014-2015, Lann Martin +Copyright (C) 2015-2016, Google +Copyright (C) 2015, Matt Farina and Matt Butcher ----- -Distributed under the terms of the MIT License (MIT) +Distributed under the terms of the MIT License ----- -dig v1.13.0 -Copyright (c) 2017-2018 Uber Technologies, Inc. +influxdata/influxdb1-client Commit b269163 +Copyright (c) 2019 InfluxData ----- -Distributed under the terms of the MIT License (MIT) +Distributed under the terms of the MIT License ----- -fx v1.16.0 -Copyright (c) 2016-2018 Uber Technologies, Inc. +sqlx 1.3.5 +Copyright (c) 2013, Jason Moiron ----- -Distributed under the terms of the MIT License (MIT) +Distributed under the terms of the MIT License ----- -influxdb1-client v0.0.0-20200827194710-b269163b24ab -Copyright (c) 2019 InfluxData +lib/pq 1.10.4 +Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany ----- -Distributed under the terms of the MIT License (MIT) +Distributed under the terms of the MIT License ----- -multierr v1.7.0 -Copyright (c) 2017-2021 Uber Technologies, Inc. +stretchr/testify 1.8.0 +Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors. ----- -Distributed under the terms of the MIT License (MIT) +Distributed under the terms of the MIT License ----- -pq v1.10.4 -Copyright (c) 2011-2013, 'pq' Contributors -Portions Copyright (C) 2011 Blake Mizerany +go.uber.org/atomic 1.10.0 +Copyright (c) 2016 Uber Technologies, Inc. ----- -Distributed under the terms of the MIT License (MIT) +Distributed under the terms of the MIT License ----- -pretty v0.0.0-20200227124842-a10e7caefd8e -Copyright 2012 Keith Rarick +go.uber.org/fx 1.18.1 +Copyright (c) 2016-2018 Uber Technologies, Inc. ----- -Distributed under the terms of the MIT License (MIT) +Distributed under the terms of the MIT License ----- -sqlx v1.3.4 -Copyright (c) 2013, Jason Moiron +perks 1.0.1 +Copyright (C) 2013 Blake Mizerany ----- -Distributed under the terms of the MIT License (MIT) +Distributed under the terms of the MIT License ----- -squirrel v1.5.2 -Copyright (C) 2014-2015, Lann Martin -Copyright (C) 2015-2016, Google -Copyright (C) 2015, Matt Farina and Matt Butcher +github.com/cespare/xxhash/v2 2.1.2 +Copyright (c) 2016 Caleb Spare ----- -Distributed under the terms of the MIT License (MIT) +Distributed under the terms of the MIT License ----- -testify v1.7.0 -Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors. +kr/text 0.2.0 +Copyright 2012 Keith Rarick ----- -Distributed under the terms of the MIT License (MIT) +Distributed under the terms of the MIT License ----- -text v0.2.0 -Copyright 2012 Keith Rarick +lann/builder Commit 47ae307 +Copyright (c) 2014-2015 Lann Martin ----- -Distributed under the terms of the MIT License (MIT) +Distributed under the terms of the MIT License ----- -zap v1.20.0 -Copyright (c) 2016-2017 Uber Technologies, Inc. +lann/ps Commit 62de8c4 +Copyright (c) 2013 Michael Hendricks ----- -Distributed under the terms of the MIT License (MIT) +Distributed under the terms of the MIT License ----- -== -the MIT License -== -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the"Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -check.v1 v1.0.0-20200227125254-8fa46927fb4f -Copyright (c) 2010-2013 Gustavo Niemeyer . +niemeyer/pretty Commit a10e7ca +Copyright 2012 Keith Rarick ----- -Distributed under the terms of the BSD License (two-clause) +Distributed under the terms of the MIT License ----- -This library contains Benchmark.go distributed under the following terms: -Copyright (c) 2012 The Go Authors. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -errors v0.9.1 -Copyright (c) 2015, Dave Cheney +go.uber.org/dig 1.15.0 +Copyright (c) 2017-2018 Uber Technologies, Inc. ----- -Distributed under the terms of the BSD License (two-clause) +Distributed under the terms of the MIT License ----- -== -The BSD License (two-clause) -== -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +go.uber.org/multierr 1.8.0 +Copyright (c) 2017-2021 Uber Technologies, Inc. +----- +Distributed under the terms of the MIT License +----- -uuid v1.3.0 -Copyright (c) 2009,2014 Google Inc. All rights reserved. +go.uber.org/zap 1.23.0 +Copyright (c) 2016-2017 Uber Technologies, Inc. ----- -Distributed under the terms of the BSD License (three-clause) +Distributed under the terms of the MIT License ----- == -the BSD License (three-clause) +the MIT License == -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the"Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +golang/mock 1.6.0 +Copyright 2019 Google LLC +----- +Distributed under the terms of the Apache License Version 2.0 +----- -client_golang v1.12.1 -Prometheus instrumentation library for Go applications +prometheus/client_golang 1.12.1 Copyright 2012-2015 The Prometheus Authors ----- Distributed under the terms of the Apache License Version 2.0 ----- -This product includes software developed at SoundCloud Ltd. (http://soundcloud.com/). +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). The following components are included in this product: @@ -171,47 +156,78 @@ https://github.com/matttproud/golang_protobuf_extensions Copyright 2013 Matt T. Proud Licensed under the Apache License, Version 2.0 +go.opentelemetry.io/otel 1.10.0 +Copyright The OpenTelemetry Authors +----- +Distributed under the terms of the Apache License Version 2.0 +----- -mock v1.6.0 -Alex Reece -Google Inc. +go.opentelemetry.io/otel/exporters/stdout/stdouttrace 1.10.0 +Copyright The OpenTelemetry Authors ----- Distributed under the terms of the Apache License Version 2.0 ----- -otel v1.3.0 +go.opentelemetry.io/otel/sdk 1.10.0 Copyright The OpenTelemetry Authors ----- Distributed under the terms of the Apache License Version 2.0 ----- -sdk v1.3.0 +go.opentelemetry.io/otel/trace 1.10.0 Copyright The OpenTelemetry Authors ----- Distributed under the terms of the Apache License Version 2.0 ----- -stdouttrace v1.3.0 -Copyright The OpenTelemetry Authors +go-logr/logr 1.2.3 +Copyright 2019 The logr Authors. ----- Distributed under the terms of the Apache License Version 2.0 ----- -trace v1.3.0 -Copyright The OpenTelemetry Authors +go-logr/stdr 1.2.2 +Copyright 2019 The logr Authors. +----- +Distributed under the terms of the Apache License Version 2.0 +----- + + +golang_protobuf_extensions 1.0.1 +Copyright 2012 Matt T. Proud (matt.proud@gmail.com) +----- +Distributed under the terms of the Apache License Version 2.0 +----- + + +prometheus/client_model 0.2.0 +Copyright 2012-2015 The Prometheus Authors +----- +Distributed under the terms of the Apache License Version 2.0 +----- + + +github.com/prometheus/common 0.32.1 +Copyright 2015 The Prometheus Authors +----- +Distributed under the terms of the Apache License Version 2.0 +----- +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). + + +prometheus/procfs 0.7.3 +Copyright 2014-2015 The Prometheus Authors ----- Distributed under the terms of the Apache License Version 2.0 ----- -== -the Apache License Version 2.0 -== Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -240,25 +256,116 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +github.com/google/uuid 1.3.0 +Copyright (c) 2009,2014 Google Inc. All rights reserved. +----- +Distributed under the terms of the BSD License (three-clause) +----- + + +golang/protobuf 1.5.2 +Copyright 2019 The Go Authors + +----- +Distributed under the terms of the BSD License (three-clause) ----- -errwrap v1.1.0 -https://github.com/hashicorp/errwrap/tree/v1.1.0 +github.com/pmezard/go-difflib 1.0.0 +Copyright (c) 2013, Patrick Mezard +All rights reserved. ----- -Distributed under the terms of the Mozilla Public License, version 2.0 +Distributed under the terms of the BSD License (three-clause) ----- -go-multierror v1.1.1 -https://github.com/hashicorp/go-multierror/tree/v1.1.1 +golang.org/x/sys commit da31bd327af9 +Copyright (c) 2009 The Go Authors. All rights reserved. +----- +Distributed under the terms of the BSD License (three-clause) ----- -Distributed under the terms of the Mozilla Public License, version 2.0 +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. + +google.golang.org/protobuf 1.26.0 +Copyright (c) 2018 The Go Authors. All rights reserved. ----- +Distributed under the terms of the BSD License (three-clause) +----- +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. == -the Mozilla Public License, version 2.0 +the BSD License (three-clause) == +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +hashicorp/go-multierror 1.1.1 +https://github.com/hashicorp/go-multierror +----- +Distributed under the terms of the Mozilla Public License 2.0 +----- + + +go-version 1.6.0 +https://github.com/hashicorp/go-version +----- +Distributed under the terms of the Mozilla Public License 2.0 +----- + + +hashicorp/errwrap v1.1.0 +https://github.com/hashicorp/errwrap +----- +Distributed under the terms of the Mozilla Public License 2.0 +----- + + Mozilla Public License, version 2.0 1. Definitions 1.1. “Contributor” means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. @@ -341,4 +448,86 @@ This Source Code Form is subject to the terms of the Mozilla Public License, v. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - “Incompatible With Secondary Licenses” Notice -This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. \ No newline at end of file +This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. + +pkg/errors 0.9.1 +Copyright (c) 2015, Dave Cheney +All rights reserved. +----- +Distributed under the terms of the BSD License (two-clause) +----- + + +gopkg.in/check.v1 commit 8fa46927fb4f +Copyright (c) 2010-2013 Gustavo Niemeyer +All rights reserved. +----- +Distributed under the terms of the BSD License (two-clause) +----- + + +The BSD 2-Clause License +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +davecgh/go-spew 1.1.1 +Copyright (c) 2012-2016 Dave Collins +----- +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +----- + + +yaml 3.0.1 +This project is covered by two different licenses: MIT and Apache. + +#### MIT License #### + +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original MIT license, with the additional +copyright staring in 2011 when the project was ported over: + + apic.go emitterc.go parserc.go readerc.go scannerc.go + writerc.go yamlh.go yamlprivateh.go + +Copyright (c) 2006-2010 Kirill Simonov +Copyright (c) 2006-2011 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +### Apache License ### + +All the remaining project files are covered by the Apache license: + +Copyright (c) 2011-2019 Canonical Ltd + +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. +----- \ No newline at end of file diff --git a/log_levels.go b/log_levels.go index 79d0307..6187cc7 100644 --- a/log_levels.go +++ b/log_levels.go @@ -5,6 +5,8 @@ type LogLevel int func (l LogLevel) String() string { switch l { + case Debug: + return "D" case Info: return "I" case Warn: @@ -19,6 +21,7 @@ func (l LogLevel) String() string { } const ( + Debug LogLevel = -1 Info LogLevel = 0 Warn LogLevel = 1 Error LogLevel = 2 diff --git a/span.go b/span.go index 53e7731..a863a5f 100644 --- a/span.go +++ b/span.go @@ -47,15 +47,34 @@ type Span struct { } // Start preferred way to start a new span, automatically sets basic span fields like class, name, host -func Start(ctx1 context.Context, opts ...SpanOption) (span *Span, ctx context.Context) { +func Start(ctx context.Context, opts ...SpanOption) (*Span, context.Context) { + return startInternal(ctx, opts...) +} + +// StartLeaf start new span without returning resulting context i.e. no child spans possibility +func StartLeaf(ctx context.Context, opts ...SpanOption) *Span { + span, _ := startInternal(ctx, opts...) + return span +} + +// Message is the simplest way to start a span, in the shortest way possible +// it doesn't use context, and doesn't return one. +// It is strongly discouraged to use Message unless for testing or showing off purposes. +func Message(message string, opts ...SpanOption) *Span { + span, _ := startInternal(context.Background(), opts...) + span.Message(message) + return span +} + +func startInternal(ctx1 context.Context, opts ...SpanOption) (span *Span, ctx context.Context) { span = &Span{ - id: NewSpanID(), - host: host, - startedTs: time.Now(), tags: map[string]interface{}{}, vals: map[string]interface{}{}, propagatedTags: map[string]interface{}{}, } + for _, opt := range SpanDefaults { + opt.apply(span) + } if p := CtxActiveSpan(ctx1); p != nil { span.parent = p @@ -74,7 +93,7 @@ func Start(ctx1 context.Context, opts ...SpanOption) (span *Span, ctx context.Co } if span.packageName == "" || span.className == "" || span.name == "" { - packageName, className, funcName := reflectutil.GetPackageClassFunc() + packageName, className, funcName := reflectutil.GetPackageClassFunc(3) if span.packageName == "" { span.packageName = packageName } @@ -89,41 +108,6 @@ func Start(ctx1 context.Context, opts ...SpanOption) (span *Span, ctx context.Co return span, context.WithValue(ctx1, activeSpanKey{}, span) } -// StartLeaf start new span without returning resulting context i.e. no child spans possibility -func StartLeaf(ctx context.Context, opts ...SpanOption) (span *Span) { - packageName, className, funcName := reflectutil.GetPackageClassFunc() - span, _ = Start(ctx, append([]SpanOption{ - WithName(funcName), - WithPackageClass(packageName, className), - }, opts...)...) - span.packageName = packageName - span.className = className - return span -} - -// Message is the simplest way to start a span, in the shortest way possible -// it doesn't use context, and doesn't return one. -// It is strongly discouraged to use Message unless for testing purposes. -func Message(message string, opts ...SpanOption) *Span { - packageName, className, funcName := reflectutil.GetPackageClassFunc() - span, _ := Start(context.Background(), append([]SpanOption{ - WithPackageClass(packageName, className), - WithName(funcName), - }, opts...)...) - span.Message(message) - return span -} - -// StartFromParentID starts new span with externally defined parent span ID -// Deprecated: use SpanOptions -func StartFromParentID(ctx context.Context, parentSpanID SpanID, traceID TraceID) (*Span, context.Context) { - p, c, f := reflectutil.GetPackageClassFunc() - span, ctx := Start(ctx, WithPackageClass(p, c), WithName(f)) - span.parentID = parentSpanID - span.traceID = traceID - return span, ctx -} - func (s *Span) ID() SpanID { return s.id } @@ -136,13 +120,14 @@ func (s *Span) Parent() *Span { return s.parent } -func (s *Span) Stop() { +func (s *Span) Stop() *Span { // no need to sync this, as the race won't matter if !s.finishedTs.IsZero() { - return + return s } s.finishedTs = time.Now() s.duration = s.finishedTs.Sub(s.startedTs) + return s } func (s *Span) IsFinished() bool { @@ -160,6 +145,10 @@ func (s *Span) StartedTs() time.Time { return s.startedTs } +func (s *Span) FinishedTs() time.Time { + return s.finishedTs +} + func (s *Span) Host() string { return s.host } @@ -290,12 +279,14 @@ type Enricher interface { Enrich(span *Span) *Span } -func (s *Span) EnrichFrom(e Enricher) *Span { - e.Enrich(s) +func (s *Span) EnrichFrom(ee ...Enricher) *Span { + for _, e := range ee { + e.Enrich(s) + } return s } -// Err adds error to the span, subsequent call combined errors +// Err adds error to the span, subsequent call combined errors, returns all combined errors func (s *Span) Err(err error) error { if err == nil { return nil @@ -305,7 +296,7 @@ func (s *Span) Err(err error) error { return err } s.errs = errs.Append(s.errs, err) - return err + return s.errs } // ErrWrapf shorthand for errors wrap @@ -335,6 +326,15 @@ func (s *Span) ErrSpan(err error) *Span { return s } +// ErrFinish convenience method to flush span with error and ignore otherwise +func (s *Span) ErrFinish(err error, trs Tracer) error { + if err == nil { + return nil + } + s.ErrSpan(err).FlushTo(trs) + return err +} + // DeferErr adds defer errors to span. Not the same as Err! func (s *Span) DeferErr(err error) *Span { if err == nil { @@ -368,9 +368,8 @@ func (s *Span) WarnWith(err error) error { return err } -// Message shorthand for generic Val("message", ... ) value -// overwrites previous message -// usage of specific tags and values is preferred! +// Message shorthand for generic Val("message", ... ) value, overwrites previous message +// usage of plain text messages is discouraged, use tags and values! func (s *Span) Message(message string) *Span { return s.Val("message", message) } @@ -382,6 +381,12 @@ func (s *Span) Level(level LogLevel) *Span { return s } +// LevelGet enhancement for better exporters and serializers implementations +// issue https://github.com/KasperskyLab/klogga/issues/7 +func (s *Span) LevelGet() LogLevel { + return s.level +} + // Tags get a copy of span tags func (s *Span) Tags() map[string]interface{} { result := make(map[string]interface{}) @@ -529,48 +534,18 @@ func (s *Span) EWState() string { return res } -func (s *Span) Json() ([]byte, error) { - if s == nil { - return nil, nil - } - - jsonStruct := struct { - ID SpanID - ParentID SpanID - TraceID TraceID - Ts string - Level string - PackageClass string - Name string - Duration time.Duration - Error error - DeferError error - Warn error - Tags map[string]interface{} - Vals map[string]interface{} - }{ - ID: s.id, - ParentID: s.parentID, - TraceID: s.traceID, - Ts: s.startedTs.Format(TimestampLayout), - Level: s.EWState(), - PackageClass: s.PackageClass(), - Name: s.name, - Duration: s.Duration(), - Error: s.Errs(), - DeferError: s.DeferErrs(), - Warn: s.Warns(), - Tags: s.Tags(), - Vals: s.Vals(), - } - return json.Marshal(&jsonStruct) -} - // FlushTo accept tracer and call trs.Finish, shorthand for chaining func (s *Span) FlushTo(trs Tracer) { trs.Finish(s) } +// FlushOnError if span has errors accept tracer and call trs.Finish +func (s *Span) FlushOnError(trs Tracer) { + if s.HasErr() || s.HasDeferErr() { + trs.Finish(s) + } +} + // CreateErrSpanFrom creates span describing an error in a flat way func CreateErrSpanFrom(ctx context.Context, span *Span) *Span { if !span.HasErr() { @@ -579,11 +554,13 @@ func CreateErrSpanFrom(ctx context.Context, span *Span) *Span { errSpan := StartLeaf(ctx, WithTraceID(span.TraceID())) errSpan.parent = span + errSpan.parentID = span.ID() errSpan.startedTs = span.StartedTs() errSpan.Tag("component", span.Component()) errSpan.host = span.Host() - errSpan.name = span.Name() + errSpan.packageName = span.Package() errSpan.className = span.Class() + errSpan.name = span.Name() errSpan.errs = span.errs errSpan.warns = span.warns errSpan.deferErrs = span.deferErrs diff --git a/span_defaults.go b/span_defaults.go new file mode 100644 index 0000000..7fdd293 --- /dev/null +++ b/span_defaults.go @@ -0,0 +1,10 @@ +package klogga + +// SpanDefaults is applied to all spans by default, before all other options +// initially created in response to https://github.com/KasperskyLab/klogga/issues/5 +// modify to change the default options +var SpanDefaults = []SpanOption{ + WithNewSpanID(), + WithTimestampNow(), + WithHostName(), +} diff --git a/span_fuzz_test.go b/span_fuzz_test.go new file mode 100644 index 0000000..dd20d19 --- /dev/null +++ b/span_fuzz_test.go @@ -0,0 +1,18 @@ +package klogga + +import ( + "context" + "testing" +) + +func FuzzTags(f *testing.F) { + f.Add("t1", "danila1") + f.Add("t2", "danila2") + f.Fuzz(func(t *testing.T, tag, tagValue string) { + spanStr := StartLeaf(context.Background()).Tag(tag, tagValue). + Stringify() + if spanStr == "" { + t.Errorf("tag=%s tagValue=%s", tag, tagValue) + } + }) +} diff --git a/span_json.go b/span_json.go new file mode 100644 index 0000000..8d0d498 --- /dev/null +++ b/span_json.go @@ -0,0 +1,42 @@ +package klogga + +import ( + "encoding/json" + "github.com/KasperskyLab/klogga/util/errs" +) + +func (s *Span) MarshalJSON() ([]byte, error) { + if s == nil { + return nil, nil + } + + jsonMap := map[string]any{} + + for k, v := range s.Tags() { + jsonMap[k] = v + } + for k, v := range s.Vals() { + jsonMap[k] = v + } + + jsonMap["id"] = s.ID() + jsonMap["parent_id"] = s.ParentID() + jsonMap["trace_id"] = s.TraceID() + jsonMap["started"] = s.StartedTs().Format(TimestampLayout) + jsonMap["duration"] = s.Duration() + jsonMap["level"] = s.level.String() + jsonMap["component"] = s.component + jsonMap["package_class"] = s.PackageClass() + jsonMap["name"] = s.Name() + jsonMap["error"] = errs.Append(s.Errs(), s.DeferErrs()) + jsonMap["warn"] = s.Warns() + jsonMap["tags"] = s.Tags() + jsonMap["vals"] = s.Vals() + + return json.Marshal(jsonMap) +} + +// Json DEPRECATED for compatibility with earlier versions +func (s *Span) Json() ([]byte, error) { + return s.MarshalJSON() +} diff --git a/span_opt.go b/span_opt.go index 1ca5418..68b08ee 100644 --- a/span_opt.go +++ b/span_opt.go @@ -6,6 +6,10 @@ type SpanOption interface { apply(*Span) } +type SpanOptionFunc func(*Span) + +func (f SpanOptionFunc) apply(span *Span) { f(span) } + type withTimestampOption struct { ts time.Time } @@ -20,6 +24,30 @@ func (o withTimestampOption) apply(span *Span) { span.startedTs = o.ts } +func WithTimestampUtcNow() SpanOption { + return (SpanOptionFunc)(func(span *Span) { + span.startedTs = time.Now().UTC() + }) +} + +func WithTimestampNow() SpanOption { + return (SpanOptionFunc)(func(span *Span) { + span.startedTs = time.Now() + }) +} + +func WithNewSpanID() SpanOption { + return (SpanOptionFunc)(func(span *Span) { + span.id = NewSpanID() + }) +} + +func WithHostName() SpanOption { + return (SpanOptionFunc)(func(span *Span) { + span.host = host + }) +} + type withNameOption struct { name string } diff --git a/span_test.go b/span_test.go index dc40ae6..4c68c35 100644 --- a/span_test.go +++ b/span_test.go @@ -54,7 +54,7 @@ func TestSpanJson(t *testing.T) { require.Empty(t, span.Component()) - bb, err := span.Json() + bb, err := span.MarshalJSON() require.NoError(t, err) str := string(bb) require.Contains(t, str, "danila") diff --git a/testdata/log.std.out.v5/logger.go b/testdata/log.std.out.v5/logger.go new file mode 100644 index 0000000..c6ec4e0 --- /dev/null +++ b/testdata/log.std.out.v5/logger.go @@ -0,0 +1,21 @@ +package loggers + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/loggers.v4.0.1/logger.go b/testdata/loggers.v4.0.1/logger.go new file mode 100644 index 0000000..c6ec4e0 --- /dev/null +++ b/testdata/loggers.v4.0.1/logger.go @@ -0,0 +1,21 @@ +package loggers + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/loggers.v4/logger.go b/testdata/loggers.v4/logger.go new file mode 100644 index 0000000..c6ec4e0 --- /dev/null +++ b/testdata/loggers.v4/logger.go @@ -0,0 +1,21 @@ +package loggers + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/loggers/2.2.2/logger.go b/testdata/loggers/2.2.2/logger.go new file mode 100644 index 0000000..c6ec4e0 --- /dev/null +++ b/testdata/loggers/2.2.2/logger.go @@ -0,0 +1,21 @@ +package loggers + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/loggers/logger.go b/testdata/loggers/logger.go new file mode 100644 index 0000000..c6ec4e0 --- /dev/null +++ b/testdata/loggers/logger.go @@ -0,0 +1,21 @@ +package loggers + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/loggers/v2.1/logger.go b/testdata/loggers/v2.1/logger.go new file mode 100644 index 0000000..c6ec4e0 --- /dev/null +++ b/testdata/loggers/v2.1/logger.go @@ -0,0 +1,21 @@ +package loggers + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/loggers/v2/logger.go b/testdata/loggers/v2/logger.go new file mode 100644 index 0000000..c6ec4e0 --- /dev/null +++ b/testdata/loggers/v2/logger.go @@ -0,0 +1,21 @@ +package loggers + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/loggersv3/logger.go b/testdata/loggersv3/logger.go new file mode 100644 index 0000000..c6ec4e0 --- /dev/null +++ b/testdata/loggersv3/logger.go @@ -0,0 +1,21 @@ +package loggers + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/nested/logger.go b/testdata/nested/logger.go new file mode 100644 index 0000000..722396e --- /dev/null +++ b/testdata/nested/logger.go @@ -0,0 +1,21 @@ +package nested + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/nested/very/deep/logger.go b/testdata/nested/very/deep/logger.go new file mode 100644 index 0000000..7e9c486 --- /dev/null +++ b/testdata/nested/very/deep/logger.go @@ -0,0 +1,21 @@ +package deep + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/nested/very/deep/v4/black/hole/logger.go b/testdata/nested/very/deep/v4/black/hole/logger.go new file mode 100644 index 0000000..cb8ff88 --- /dev/null +++ b/testdata/nested/very/deep/v4/black/hole/logger.go @@ -0,0 +1,21 @@ +package hole + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/nested/very/deep/v4/black/hole/v6/loggers/logger.go b/testdata/nested/very/deep/v4/black/hole/v6/loggers/logger.go new file mode 100644 index 0000000..c6ec4e0 --- /dev/null +++ b/testdata/nested/very/deep/v4/black/hole/v6/loggers/logger.go @@ -0,0 +1,21 @@ +package loggers + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/nested/very/deep/v4/black/hole/v6/loggers/v7/logger.go b/testdata/nested/very/deep/v4/black/hole/v6/loggers/v7/logger.go new file mode 100644 index 0000000..c6ec4e0 --- /dev/null +++ b/testdata/nested/very/deep/v4/black/hole/v6/loggers/v7/logger.go @@ -0,0 +1,21 @@ +package loggers + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/nested/very/deep/v4/black/logger.go b/testdata/nested/very/deep/v4/black/logger.go new file mode 100644 index 0000000..7b9f784 --- /dev/null +++ b/testdata/nested/very/deep/v4/black/logger.go @@ -0,0 +1,21 @@ +package black + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/nested/very/logger.go b/testdata/nested/very/logger.go new file mode 100644 index 0000000..587007e --- /dev/null +++ b/testdata/nested/very/logger.go @@ -0,0 +1,21 @@ +package very + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/package_test.go b/testdata/package_test.go new file mode 100644 index 0000000..ffedf13 --- /dev/null +++ b/testdata/package_test.go @@ -0,0 +1,79 @@ +package testdata + +import ( + "context" + "github.com/KasperskyLab/klogga" + loggers5 "github.com/KasperskyLab/klogga/testdata/log.std.out.v5" + "github.com/KasperskyLab/klogga/testdata/loggers" + loggers4 "github.com/KasperskyLab/klogga/testdata/loggers.v4" + loggers401 "github.com/KasperskyLab/klogga/testdata/loggers.v4.0.1" + loggers222 "github.com/KasperskyLab/klogga/testdata/loggers/2.2.2" + loggers2 "github.com/KasperskyLab/klogga/testdata/loggers/v2" + loggers21 "github.com/KasperskyLab/klogga/testdata/loggers/v2.1" + loggersv3 "github.com/KasperskyLab/klogga/testdata/loggersv3" + "github.com/KasperskyLab/klogga/testdata/nested" + "github.com/KasperskyLab/klogga/testdata/nested/very" + "github.com/KasperskyLab/klogga/testdata/nested/very/deep" + "github.com/KasperskyLab/klogga/testdata/nested/very/deep/v4/black" + "github.com/KasperskyLab/klogga/testdata/nested/very/deep/v4/black/hole" + loggersv6 "github.com/KasperskyLab/klogga/testdata/nested/very/deep/v4/black/hole/v6/loggers" + loggersv7 "github.com/KasperskyLab/klogga/testdata/nested/very/deep/v4/black/hole/v6/loggers/v7" + "github.com/KasperskyLab/klogga/testdata/parent/child" + "github.com/stretchr/testify/require" + "testing" +) + +func TestVersionPackages(t *testing.T) { + type iLogger interface { + Log(trsFactory klogga.TracerProvider) + Log2(trsFactory klogga.TracerProvider) + Message(trsFactory klogga.TracerProvider) + } + for _, ts := range []struct { + logger iLogger + expect string + }{ + {logger: new(loggers.Logger), expect: "loggers"}, + {logger: new(loggers2.Logger), expect: "loggers"}, + {logger: new(loggers4.Logger), expect: "loggers"}, + {logger: new(loggers401.Logger), expect: "loggers"}, + {logger: new(loggers222.Logger), expect: "loggers"}, + {logger: new(loggers21.Logger), expect: "loggers"}, + {logger: new(loggers5.Logger), expect: "log.std.out"}, + {logger: new(loggersv3.Logger), expect: "loggersv3"}, + {logger: new(loggersv6.Logger), expect: "loggers"}, + {logger: new(loggersv7.Logger), expect: "loggers"}, + {logger: new(nested.Logger), expect: "nested"}, + {logger: new(very.Logger), expect: "very"}, + {logger: new(deep.Logger), expect: "deep"}, + {logger: new(black.Logger), expect: "black"}, + {logger: new(hole.Logger), expect: "hole"}, + {logger: new(child.Logger), expect: "parent"}, + } { + tf := klogga.NewFactory(newExporter(t, ts.expect)) + ts.logger.Log(tf) + ts.logger.Log2(tf) + ts.logger.Message(tf) + } +} + +type exporter struct { + t *testing.T + expectedPackage string +} + +func newExporter(t *testing.T, expectedPackage string) *exporter { + t.Helper() + return &exporter{t: t, expectedPackage: expectedPackage} +} + +func (e exporter) Write(ctx context.Context, spans []*klogga.Span) error { + for _, span := range spans { + require.Equal(e.t, e.expectedPackage, span.Package()) + } + return nil +} + +func (e exporter) Shutdown(context.Context) error { + return nil +} diff --git a/testdata/parent/child/child.go b/testdata/parent/child/child.go new file mode 100644 index 0000000..babe98f --- /dev/null +++ b/testdata/parent/child/child.go @@ -0,0 +1,7 @@ +package child + +import "github.com/KasperskyLab/klogga/testdata/parent" + +type Logger struct { + parent.Logger +} diff --git a/testdata/parent/parent.go b/testdata/parent/parent.go new file mode 100644 index 0000000..8de4dca --- /dev/null +++ b/testdata/parent/parent.go @@ -0,0 +1,21 @@ +package parent + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +type Logger struct{} + +func (r *Logger) Log(trsFactory klogga.TracerProvider) { + klogga.StartLeaf(context.Background()).Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Log2(trsFactory klogga.TracerProvider) { + span, _ := klogga.Start(context.Background()) + span.Val("run", "test").FlushTo(trsFactory.NamedPkg()) +} + +func (r *Logger) Message(trsFactory klogga.TracerProvider) { + klogga.Message("message").FlushTo(trsFactory.NamedPkg()) +} diff --git a/testdata/testpkg/v2/span_test.go b/testdata/testpkg/v2/span_test.go new file mode 100644 index 0000000..9f77983 --- /dev/null +++ b/testdata/testpkg/v2/span_test.go @@ -0,0 +1,11 @@ +package testpkg + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestVersionedPackageName(t *testing.T) { + span := CreateMySpan() + require.Equal(t, "testpkg", span.Package()) +} diff --git a/testdata/testpkg/v2/testpkg.go b/testdata/testpkg/v2/testpkg.go new file mode 100644 index 0000000..952b232 --- /dev/null +++ b/testdata/testpkg/v2/testpkg.go @@ -0,0 +1,11 @@ +package testpkg + +import ( + "context" + "github.com/KasperskyLab/klogga" +) + +func CreateMySpan() *klogga.Span { + span, _ := klogga.Start(context.Background()) + return span +} diff --git a/util/defaults.go b/util/defaults.go new file mode 100644 index 0000000..a121caf --- /dev/null +++ b/util/defaults.go @@ -0,0 +1,10 @@ +package util + +import ( + "github.com/KasperskyLab/klogga" + "github.com/KasperskyLab/klogga/exporters/golog" +) + +func DefaultFactory() *klogga.Factory { + return klogga.NewFactory(golog.New(nil)) +} diff --git a/util/errs/errs_test.go b/util/errs/errs_test.go index c2e09e4..c244e17 100644 --- a/util/errs/errs_test.go +++ b/util/errs/errs_test.go @@ -8,11 +8,12 @@ import ( func TestAppend(t *testing.T) { require.Nil(t, Append(nil, nil)) + require.Nil(t, Append(nil, nil, nil)) require.NotNil(t, Append(errors.New("err"), nil)) require.NotNil(t, Append(nil, errors.New("err"))) } -func TestAppenManyNils(t *testing.T) { +func TestAppendManyNils(t *testing.T) { source := errors.New("err") err := Append(nil, nil, nil, source) require.NotNil(t, err) diff --git a/util/reflectutil/reflectutil.go b/util/reflectutil/reflectutil.go index aa9aca2..16dd74e 100644 --- a/util/reflectutil/reflectutil.go +++ b/util/reflectutil/reflectutil.go @@ -1,35 +1,116 @@ package reflectutil import ( + "net/url" "reflect" "runtime" "strings" + + "github.com/hashicorp/go-version" ) +const pathSeparator = string("/") + // GetPackageClassFunc parses package (last entry in the path), // class(==receiver type) and func for the call one level up -func GetPackageClassFunc() (string, string, string) { - pc := make([]uintptr, 10) - runtime.Callers(2, pc) +func GetPackageClassFunc(skip int) (string, string, string) { + pc, _, _, _ := runtime.Caller(skip) - fnc := runtime.FuncForPC(pc[1]) + fnc := runtime.FuncForPC(pc) + // We have something like "path.to/my/pkg.MyFunction". If the function is + // a closure, it is something like, "path.to/my/pkg.MyFunction.func1". fullName := fnc.Name() // remove path to package - i := strings.LastIndex(fullName, "/") - if i >= 0 && i+1 < len(fullName) { - fullName = fullName[i+1:] - } - - split := strings.Split(fullName, ".") + // Everything up to the first "." after the last "/" is the package name. + // Everything after the "." is the full function name. + shortName, rest := base(fullName, pathSeparator) + split := strings.Split(shortName, ".") if len(split) >= 3 { - return split[0], cleanReceiver(split[1]), split[2] + return ParsePackageName(rest + pathSeparator + split[0]), cleanReceiver(split[1]), split[2] } if len(split) == 2 { - return split[0], "", split[1] + return ParsePackageName(rest + pathSeparator + split[0]), "", split[1] } - return fullName, "", "" + return ParsePackageName(fullName), "", "" +} + +// ParsePackageName determines the package name based on the path to it. Recognizes and ignores library versioning +// specified via /v* or .v*. Not able to determine the real name of the package, it relies on the folder in which +// the package is located, ignoring the folder for versioning(like /v2) or its suffix(like .v2). If the package name is +// packageA and it is in the folder packageB, then the package will be recognized as packageB. +// Returns the query decoded package name +// implemented in response for issue https://github.com/KasperskyLab/klogga/issues/8 +func ParsePackageName(fullPath string) string { + pkgName, parentDir := base(fullPath, pathSeparator) + if pkgName == "" { + return pkgName + } + // Package names are URL-encoded to avoid ambiguity in the case where the + // package name contains ".git". Otherwise, "foo/bar.git.MyFunction" would + // mean that "git" is the top-level function and "MyFunction" is embedded + // inside it. + if unescaped, err := url.QueryUnescape(pkgName); err == nil { + pkgName = unescaped + } + + // https://go.dev/ref/mod#major-version-suffixes + // Starting with major version 2, module paths must have a major version suffix like /v2 that matches the major + // version. For example, if a module has the path example.com/mod at v1.0.0, it must have the path example.com/mod/v2 + // at version v2.0.0. + // + // As a special case, modules paths starting with gopkg.in/ must always have a major version suffix, even at v0 and + // v1. The suffix must start with a dot rather than a slash (for example, gopkg.in/yaml.v2). + + var foundPkgName string + + // detect /v2 version, like google-api-go-client/blob/main/slides/v1 + checkVersion := pkgName + if checkVersion[0] == 'v' { + checkVersion = checkVersion[1:] + } + if _, err := version.NewVersion(checkVersion); err == nil { + foundPkgName, _ = base(parentDir, pathSeparator) + } else { + // detect .v2 version, like gopkg.in/yaml.v2 + right, left := base(pkgName, ".v") + if right != "" { + if _, err := version.NewVersion(right); err == nil { + foundPkgName = left + } + } + } + if foundPkgName == "" { + foundPkgName = pkgName + } + + // It has been a common practice in the past to name go package repositories either with go- prefix + // (like go-bindata or go-iter,…), so remove this prefix if meet + // https://groups.google.com/g/golang-nuts/c/WMVf2Acq6JQ?pli=1 + // Also this prefix can be in suffix, like in aws/smithy-go or edsrzf/mmap-go + // Also this prefix can be golang-(hashicorp/golang-lru) or go.(satori/go.uuid) + return trimPrefix(strings.TrimSuffix(foundPkgName, "-go"), "go-", "golang-", "go.") +} + +// trimPrefix removes one of the prefixes, does not remove more than one prefix at a time +func trimPrefix(str string, prefixes ...string) string { + originLen := len(str) + for _, prefix := range prefixes { + str = strings.TrimPrefix(str, prefix) + if len(str) != originLen { + return str + } + } + return str +} + +func base(fullName, separator string) (name string, dir string) { + i := strings.LastIndex(fullName, separator) + if i < 0 || i >= len(fullName)-1 { + return fullName, "" + } + return fullName[i+len(separator):], fullName[:i] } func cleanReceiver(c string) string { @@ -40,5 +121,13 @@ func cleanReceiver(c string) string { // IsNil universal check for nil // https://medium.com/@glucn/golang-an-interface-holding-a-nil-value-is-not-nil-bb151f472cc7 func IsNil(i interface{}) bool { - return i == nil || (reflect.ValueOf(i).Kind() == reflect.Ptr && reflect.ValueOf(i).IsNil()) + if reflect.TypeOf(i) == nil { + return true + } + switch reflect.ValueOf(i).Kind() { + // this set of types was taken from reflect.Value.IsNil() + case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.UnsafePointer, reflect.Slice, reflect.Interface: + return reflect.ValueOf(i).IsNil() + } + return false } diff --git a/util/reflectutil/reflectutil_test.go b/util/reflectutil/reflectutil_test.go index 1376fe6..d788aa1 100644 --- a/util/reflectutil/reflectutil_test.go +++ b/util/reflectutil/reflectutil_test.go @@ -5,6 +5,39 @@ import ( "testing" ) +func TestIsNil(t *testing.T) { + var ( + nilObj *int + nilInterface interface{} + nilSlice []int + nilMap map[int]int + nilChan chan int + nilFunc func() + ) + for _, obj := range []interface{}{nilObj, nilInterface, nilSlice, nilMap, nilChan, nilFunc} { + require.True(t, IsNil(obj), obj) + } + + var ( + number int + str string + floatNum float32 + b byte + pointer = &nilObj + array [2]int + noneNilMap = make(map[int]int) + noneNilSlice = make([]int, 0) + noneNilChan = make(chan int) + noneNilInterface interface{} = b + noneNilFunc = func() {} + ) + for _, obj := range []interface{}{ + number, str, floatNum, b, pointer, array, noneNilMap, noneNilSlice, noneNilChan, noneNilInterface, noneNilFunc, + } { + require.False(t, IsNil(obj), obj) + } +} + func TestGetClass(t *testing.T) { c := La{} require.Equal(t, "La", c.DoStuff()) @@ -17,7 +50,7 @@ func TestGetClass(t *testing.T) { func TestGetFunc(t *testing.T) { func() { - p, c, f := GetPackageClassFunc() + p, c, f := GetPackageClassFunc(2) require.Equal(t, "reflectutil", p) require.Equal(t, "", c) require.Equal(t, "TestGetFunc", f) @@ -26,7 +59,7 @@ func TestGetFunc(t *testing.T) { // go:noinline func getClass() string { - _, c, _ := GetPackageClassFunc() + _, c, _ := GetPackageClassFunc(2) return c } diff --git a/util/testutil/timeout.go b/util/testutil/timeout.go index 4b39341..c4244db 100644 --- a/util/testutil/timeout.go +++ b/util/testutil/timeout.go @@ -5,8 +5,14 @@ import ( "time" ) -func Timeout() context.Context { +// Timeout context with timeout for tests +// optional parameter - timeout duration, default is 5 seconds +func Timeout(d ...time.Duration) context.Context { + dd := 5 * time.Second + if len(d) >= 1 { + dd = d[0] + } //nolint:govet // cancel not needed in tests - timeout, _ := context.WithTimeout(context.Background(), 5*time.Second) + timeout, _ := context.WithTimeout(context.Background(), dd) return timeout } diff --git a/utils.go b/utils.go index 4b80791..f892803 100644 --- a/utils.go +++ b/utils.go @@ -32,6 +32,8 @@ func (w WriterExporter) Shutdown(context.Context) error { } // WriterTracer adapts tracer to a Writer interface +// each call to Write writes all bytes as string to Message function +// intended as an adapter for simplest logging systems that understand only io.Writer interface type WriterTracer struct { trs Tracer }