From 27cda64d9051d40ebf20ce2f243d692b68d59b89 Mon Sep 17 00:00:00 2001 From: William Perron Date: Tue, 23 Jan 2024 08:17:13 -0500 Subject: [PATCH] mvp-1 --- builder-config.yaml | 5 + factory.go | 29 ++- go.mod | 51 ++--- go.sum | 119 ++++++----- migrations/20240120195122_init.down.sql | 4 + migrations/20240120195122_init.up.sql | 38 ++++ sqlite_exporter.go | 205 ++++++++++++++++++- sqlite_exporter_test.go | 257 ++++++++++++++++++++++++ 8 files changed, 629 insertions(+), 79 deletions(-) create mode 100644 builder-config.yaml create mode 100644 migrations/20240120195122_init.down.sql create mode 100644 migrations/20240120195122_init.up.sql create mode 100644 sqlite_exporter_test.go diff --git a/builder-config.yaml b/builder-config.yaml new file mode 100644 index 0000000..4d7c003 --- /dev/null +++ b/builder-config.yaml @@ -0,0 +1,5 @@ +# Copyright 2023 William Perron. All rights reserved. MIT License. +dist: + name: otelcol-dev + description: Basic Otel Collector for development + output_path: ./bin/otelcol-dev diff --git a/factory.go b/factory.go index 5e1f117..46679ea 100644 --- a/factory.go +++ b/factory.go @@ -3,6 +3,9 @@ package sqliteexporter import ( "context" + "database/sql" + _ "embed" + "fmt" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" @@ -11,6 +14,9 @@ import ( "go.wperron.io/sqliteexporter/internal/metadata" ) +//go:embed migrations/20240120195122_init.up.sql +var initScript string + func NewFactory() exporter.Factory { return exporter.NewFactory( metadata.Type, @@ -32,7 +38,10 @@ func createTracesExporter( ) (exporter.Traces, error) { conf := cfg.(*Config) - se := newSqliteExporter(conf) + se, err := newSqliteExporter(conf) + if err != nil { + return nil, fmt.Errorf("failed to create sqlite exporter: %w", err) + } return exporterhelper.NewTracesExporter( ctx, set, cfg, @@ -43,6 +52,20 @@ func createTracesExporter( ) } -func newSqliteExporter(cfg *Config) *sqliteExporter { - return &sqliteExporter{} +func newSqliteExporter(cfg *Config) (*sqliteExporter, error) { + db, err := sql.Open("sqlite3", cfg.Path) + if err != nil { + return nil, fmt.Errorf("couldn't open sqlite3 database: %w", err) + } + + tx, _ := db.Begin() + defer tx.Commit() + + if _, err := tx.Exec(initScript); err != nil { + return nil, fmt.Errorf("failed to run migrations: %w", err) + } + + return &sqliteExporter{ + db: db, + }, nil } diff --git a/go.mod b/go.mod index 77e5a7e..1c83c98 100644 --- a/go.mod +++ b/go.mod @@ -2,18 +2,22 @@ module go.wperron.io/sqliteexporter go 1.20 -require go.opentelemetry.io/collector/component v0.84.0 +require ( + go.opentelemetry.io/collector/component v0.92.0 + go.opentelemetry.io/collector/consumer v0.92.0 +) require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/mattn/go-sqlite3 v1.14.19 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/collector/consumer v0.84.0 // indirect - go.opentelemetry.io/collector/extension v0.84.0 // indirect - go.opentelemetry.io/collector/processor v0.84.0 // indirect - go.opentelemetry.io/collector/receiver v0.84.0 // indirect + go.opentelemetry.io/collector/config/configretry v0.92.0 // indirect + go.opentelemetry.io/collector/extension v0.92.0 // indirect + go.opentelemetry.io/collector/receiver v0.92.0 // indirect ) require ( @@ -26,26 +30,25 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.84.0 github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect - go.opentelemetry.io/collector v0.84.0 // indirect - go.opentelemetry.io/collector/config/configtelemetry v0.84.0 // indirect - go.opentelemetry.io/collector/confmap v0.84.0 // indirect - go.opentelemetry.io/collector/exporter v0.84.0 - go.opentelemetry.io/collector/featuregate v1.0.0-rcv0014 // indirect - go.opentelemetry.io/collector/pdata v1.0.0-rcv0014 // indirect - go.opentelemetry.io/otel v1.16.0 // indirect - go.opentelemetry.io/otel/metric v1.16.0 // indirect - go.opentelemetry.io/otel/trace v1.16.0 // indirect + github.com/stretchr/testify v1.8.4 + go.opentelemetry.io/collector v0.92.0 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.92.0 // indirect + go.opentelemetry.io/collector/confmap v0.92.0 + go.opentelemetry.io/collector/exporter v0.92.0 + go.opentelemetry.io/collector/featuregate v1.0.1 // indirect + go.opentelemetry.io/collector/pdata v1.0.1 + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/sdk v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.25.0 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/grpc v1.57.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/grpc v1.60.1 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 62cbd08..f9391b6 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,11 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -12,10 +15,15 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF 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-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -36,8 +44,11 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -48,10 +59,13 @@ github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPgh github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= +github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 h1:BpfhmLKZf+SjVanKKhCgf3bg+511DmU9eDQTen7LLbY= github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -61,14 +75,17 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.84.0 h1:EivvMHRis/wMhyeAo8FfGk1NS739w5+dK+qUz3TSE+E= -github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.84.0/go.mod h1:R7lpqmyo0OYBeu5OaFUwzVaO0ihMoaGGxd5BIxbl0jU= 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 v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -80,38 +97,43 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/collector v0.84.0 h1:zzsegdPlDR0iJufPsHTJhXkv9q2kbpTTTI6nTyya2wA= -go.opentelemetry.io/collector v0.84.0/go.mod h1:+cv/zxludfiiDuK3z+5eXkxAJhkCCcy8Chtvv0nOlr0= -go.opentelemetry.io/collector/component v0.84.0 h1:bh4Is5Z7TjuyF7Mab0rSNh2q3y15fImdNDRXqrqGlbA= -go.opentelemetry.io/collector/component v0.84.0/go.mod h1:uXteRaacYXXYhzeCJe5ClLz5gLzleXWz01IZ730w7FA= -go.opentelemetry.io/collector/config/configtelemetry v0.84.0 h1:pnZiYtppJN6SlzJNcrkA8R+Ur63e33qMO260m8JbK18= -go.opentelemetry.io/collector/config/configtelemetry v0.84.0/go.mod h1:+LAXM5WFMW/UbTlAuSs6L/W72WC+q8TBJt/6z39FPOU= -go.opentelemetry.io/collector/confmap v0.84.0 h1:fS62yIRrsTQwe1gyuEc8TQM0yUNfSAzPVi0A1665ZpQ= -go.opentelemetry.io/collector/confmap v0.84.0/go.mod h1:/SNHqYkLagF0TjBjQyCy2Gt3ZF6hTK8VKbaan/ZHuJs= -go.opentelemetry.io/collector/consumer v0.84.0 h1:sz8mXIdPACJArlRyFNXA1SScVoo954IU1qp9V78VUxc= -go.opentelemetry.io/collector/consumer v0.84.0/go.mod h1:Mu+KeuorwHHWd6iGxU7DMAhgsHZmmzmQgf3sSWkugmM= -go.opentelemetry.io/collector/exporter v0.84.0 h1:OWvHJghs7/R6Qwr2fh7HrgEqsIUbCgLddLQsmqN95kM= -go.opentelemetry.io/collector/exporter v0.84.0/go.mod h1:Bz7MTaVosTIz6ZISszKYLDOewjathUJuVgT5W4Ee/wU= -go.opentelemetry.io/collector/extension v0.84.0 h1:HN4otmncTE/eaeRcvBGTf0ApcX+dIQWsnShs6bgiKYA= -go.opentelemetry.io/collector/extension v0.84.0/go.mod h1:FoUzonXMAjVbFuSLM06F1260iVcbnMLMAEQk/xBfN1Y= -go.opentelemetry.io/collector/featuregate v1.0.0-rcv0014 h1:C9o0mbP0MyygqFnKueVQK/v9jef6zvuttmTGlKaqhgw= -go.opentelemetry.io/collector/featuregate v1.0.0-rcv0014/go.mod h1:0mE3mDLmUrOXVoNsuvj+7dV14h/9HFl/Fy9YTLoLObo= -go.opentelemetry.io/collector/pdata v1.0.0-rcv0014 h1:iT5qH0NLmkGeIdDtnBogYDx7L58t6CaWGL378DEo2QY= -go.opentelemetry.io/collector/pdata v1.0.0-rcv0014/go.mod h1:BRvDrx43kiSoUx3mr7SoA7h9B8+OY99mUK+CZSQFWW4= -go.opentelemetry.io/collector/processor v0.84.0 h1:6VM5HLdkroeqNZ/jvjxA4nsgweJ87FDMsnNnzVBHpcY= -go.opentelemetry.io/collector/processor v0.84.0/go.mod h1:KWgBNQuA6wmmRqJwfvPaRybK2Di9X8nE2fraGuVLNJo= -go.opentelemetry.io/collector/receiver v0.84.0 h1:5bN0A18UMAVu9AsFv5AYDgzEFEJ/nFaD1OK2rk7Ygy0= -go.opentelemetry.io/collector/receiver v0.84.0/go.mod h1:QvjpfmMUA4tW59QJBagoNEbRT5JGYZN333GAOuWIRr4= -go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= -go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= -go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= -go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= -go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= -go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.opentelemetry.io/collector v0.92.0 h1:XiC0ptaT1EmOkK2RI0gt3n2tkzLAkNQGf0E7hrGdyeA= +go.opentelemetry.io/collector v0.92.0/go.mod h1:wbksjM63DTKA1BbdUVS7gAFzAngCZTWb46RBpKdtsPw= +go.opentelemetry.io/collector/component v0.92.0 h1:/tRgPT1hr4KNB8ABHa0oJsjJFRZ5oiCIYHcTpZGwm9s= +go.opentelemetry.io/collector/component v0.92.0/go.mod h1:C2JwPTjauu36UCAzwX71/glNnOc5BR18p8FVccCFsqc= +go.opentelemetry.io/collector/config/configretry v0.92.0 h1:3WUabmCRIBHSkOLGCHGieUGchlHkBw3Fa4Cj9Do5Xdw= +go.opentelemetry.io/collector/config/configretry v0.92.0/go.mod h1:gt1HRYyMxcMca9lbDLPbivQzsUCjVjkPAn/3S6fiD14= +go.opentelemetry.io/collector/config/configtelemetry v0.92.0 h1:iCfxJ2DhWVOAHpGgkWUZRfUvUPyWGhpVRCqjPQ2D87Y= +go.opentelemetry.io/collector/config/configtelemetry v0.92.0/go.mod h1:2XLhyR/GVpWeZ2K044vCmrvH/d4Ewt0aD/y46avZyMU= +go.opentelemetry.io/collector/confmap v0.92.0 h1:xz20zNIvF9ZA1eWE+MZmZunmdXPIP/fr33ZvU0QUSxg= +go.opentelemetry.io/collector/confmap v0.92.0/go.mod h1:CmqTszB2uwiJ9ieEqISdecuoVuyt3jMnJ/9kD53GYHs= +go.opentelemetry.io/collector/consumer v0.92.0 h1:twa8T0iR9KVglvRbwZ5OPKLXPCC2DO6gVhrgDZ47MPE= +go.opentelemetry.io/collector/consumer v0.92.0/go.mod h1:fBZqP7bou3I7pDhWjleBuzdaLfQgJBc92wPJVOcKaGU= +go.opentelemetry.io/collector/exporter v0.92.0 h1:z6u+/hswJUuZbuPYIF2gXMZsqjIDd/tJO60XjLM850U= +go.opentelemetry.io/collector/exporter v0.92.0/go.mod h1:54ODYn1weY/Wr0bdxESj4P1fgyX+zaUsnJJnafORqIY= +go.opentelemetry.io/collector/extension v0.92.0 h1:zaehgW+LXCMNEb1d6Af/VHWphh5ZwX9aZl+NuQLGhpQ= +go.opentelemetry.io/collector/extension v0.92.0/go.mod h1:5EYwiaGU6deSY8YWqT5gvlnD850yJXP3NqFRKVVbYLs= +go.opentelemetry.io/collector/featuregate v1.0.1 h1:ok//hLSXttBbyu4sSV1pTx1nKdr5udSmrWy5sFMIIbM= +go.opentelemetry.io/collector/featuregate v1.0.1/go.mod h1:QQXjP4etmJQhkQ20j4P/rapWuItYxoFozg/iIwuKnYg= +go.opentelemetry.io/collector/pdata v1.0.1 h1:dGX2h7maA6zHbl5D3AsMnF1c3Nn+3EUftbVCLzeyNvA= +go.opentelemetry.io/collector/pdata v1.0.1/go.mod h1:jutXeu0QOXYY8wcZ/hege+YAnSBP3+jpTqYU1+JTI5Y= +go.opentelemetry.io/collector/receiver v0.92.0 h1:TRz4ufr5bFEszpAWgYVEx/b7VPZzEcECsyMzztf5PsQ= +go.opentelemetry.io/collector/receiver v0.92.0/go.mod h1:bYAAYbMuUVj3wx7ave2iyyJ+aGUpACliYOQ5xI92I7k= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/exporters/prometheus v0.44.1-0.20231201153405-6027c1ae76f2 h1:TnhkxGJ5qPHAMIMI4r+HPT/BbpoHxqn4xONJrok054o= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= -go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -130,8 +152,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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= @@ -142,12 +164,12 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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= @@ -165,16 +187,15 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= 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= @@ -186,9 +207,11 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD 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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= diff --git a/migrations/20240120195122_init.down.sql b/migrations/20240120195122_init.down.sql new file mode 100644 index 0000000..e177fa4 --- /dev/null +++ b/migrations/20240120195122_init.down.sql @@ -0,0 +1,4 @@ +-- Copyright 2023 William Perron. All rights reserved. MIT License. +DROP TABLE links; +DROP TABLE events; +DROP TABLE spans; diff --git a/migrations/20240120195122_init.up.sql b/migrations/20240120195122_init.up.sql new file mode 100644 index 0000000..11568da --- /dev/null +++ b/migrations/20240120195122_init.up.sql @@ -0,0 +1,38 @@ +-- Copyright 2023 William Perron. All rights reserved. MIT License. +CREATE TABLE IF NOT EXISTS spans( + "span_id" BLOB, + "trace_id" BLOB, + "parent_span_id" BLOB, + "tracestate" TEXT, + "__service_name" TEXT, + "__duration" INTEGER, + "name" TEXT, + "kind" TEXT, + "start_time" INTEGER, + "end_time" INTEGER, + "status_code" INTEGER, + "status_description" TEXT, + "attributes" TEXT, + "dropped_attributes_count" INTEGER, + "resource_attributes" TEXT, + "resource_dropped_attributes_count" INTEGER, + PRIMARY KEY ("span_id", "trace_id") +); + +CREATE TABLE IF NOT EXISTS events( + "span_id" BLOB, + "timestamp" INTEGER, + "name" TEXT, + "attributes" TEXT, + "dropped_attributes_count" INTEGER, + FOREIGN KEY ("span_id") REFERENCES spans("span_id") +); + +CREATE TABLE IF NOT EXISTS links( + "parent_span_id" BLOB, + "span_id" BLOB, + "trace_id" BLOB, + "tracestate" TEXT, + "attributes" TEXT, + "dropped_attributes_count" INTEGER +); diff --git a/sqlite_exporter.go b/sqlite_exporter.go index dc3099a..b2c1cea 100644 --- a/sqlite_exporter.go +++ b/sqlite_exporter.go @@ -3,12 +3,19 @@ package sqliteexporter import ( "context" + "database/sql" + "encoding/json" + "fmt" + _ "github.com/mattn/go-sqlite3" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/ptrace" ) -type sqliteExporter struct{} +type sqliteExporter struct { + db *sql.DB +} // Start tells the component to start. Host parameter can be used for communicating // with the host after Start() has already returned. If an error is returned by @@ -22,7 +29,7 @@ type sqliteExporter struct{} // to Start() function since that context will be cancelled soon and can abort the long-running // operation. Create a new context from the context.Background() for long-running operations. func (e *sqliteExporter) Start(ctx context.Context, host component.Host) error { - panic("not implemented") // TODO: Implement + return nil } // Shutdown is invoked during service shutdown. After Shutdown() is called, if the component @@ -42,9 +49,199 @@ func (e *sqliteExporter) Start(ctx context.Context, host component.Host) error { // the same or different configuration may be created and started (this may happen // for example if we want to restart the component). func (e *sqliteExporter) Shutdown(ctx context.Context) error { - panic("not implemented") // TODO: Implement + return e.db.Close() } +const insertSpanQ string = `INSERT INTO spans +( + span_id, + trace_id, + parent_span_id, + tracestate, + __service_name, + __duration, + name, + kind, + start_time, + end_time, + status_code, + status_description, + attributes, + dropped_attributes_count, + resource_attributes, + resource_dropped_attributes_count +) +VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, json(?), ?, json(?), ? +); +` + +const insertEventQ string = `INSERT INTO events +( + span_id, + timestamp, + name, + attributes, + dropped_attributes_count +) +VALUES ( + ?, ?, ?, json(?), ? +); +` + +const insertLinkQ string = `INSERT INTO links +( + parent_span_id, + span_id, + trace_id, + tracestate, + attributes, + dropped_attributes_count +) +VALUES ( + ?, ?, ?, ?, json(?), ? +)` + func (e *sqliteExporter) ConsumeTraces(ctx context.Context, traces ptrace.Traces) error { - panic("not implemented") + tx, err := e.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer tx.Commit() + + e.db.Begin() + + for i := 0; i < traces.ResourceSpans().Len(); i++ { + resource := traces.ResourceSpans().At(i) + + svc := "unknown" + resource.Resource().Attributes().Range(func(k string, v pcommon.Value) bool { + if k == "service.name" { + svc = v.Str() + if svc == "" { // protect against service name being another type for some reason + svc = "unknown" + } + return false + } + return true + }) + + rattrs, err := pcommonMapAsJSON(resource.Resource().Attributes()) + if err != nil { + return fmt.Errorf("failed to marshal resource attributes as json: %w", err) + } + + for j := 0; j < resource.ScopeSpans().Len(); j++ { + scope := resource.ScopeSpans().At(j) + for k := 0; k < scope.Spans().Len(); k++ { + span := scope.Spans().At(k) + + dur := span.EndTimestamp().AsTime().Sub(span.StartTimestamp().AsTime()) + + stmt, err := tx.PrepareContext(ctx, insertSpanQ) + if err != nil { + return fmt.Errorf("failed to prepare span insert stmt: %w", err) + } + + attrs, err := pcommonMapAsJSON(span.Attributes()) + if err != nil { + return fmt.Errorf("failed to marshal attributes as json: %w", err) + } + + spanidraw := [8]byte(span.SpanID()) + spanidbs := spanidraw[:] + traceidraw := [16]byte(span.TraceID()) + traceidbs := traceidraw[:] + parentidraw := [8]byte(span.ParentSpanID()) + parentidbs := parentidraw[:] + if span.ParentSpanID().IsEmpty() { + parentidbs = nil + } + + _, err = stmt.ExecContext(ctx, + spanidbs, + traceidbs, + parentidbs, + span.TraceState().AsRaw(), + svc, + dur.Microseconds(), + span.Name(), + span.Kind().String(), + span.StartTimestamp().AsTime().Unix(), + span.EndTimestamp().AsTime().Unix(), + span.Status().Code(), + span.Status().Message(), + attrs, + span.DroppedAttributesCount(), + rattrs, + resource.Resource().DroppedAttributesCount(), + ) + if err != nil { + return fmt.Errorf("error occured while inserting span: %w", err) + } + + for l := 0; l < span.Events().Len(); l++ { + event := span.Events().At(l) + + attrs, err := pcommonMapAsJSON(event.Attributes()) + if err != nil { + return fmt.Errorf("failed to marshal event attributes as json: %w", err) + } + + stmt, err := tx.PrepareContext(ctx, insertEventQ) + if err != nil { + return fmt.Errorf("failed to prepare event insert query: %w", err) + } + + _, err = stmt.ExecContext(ctx, + spanidbs, + event.Timestamp().AsTime().Unix(), + event.Name(), + attrs, + event.DroppedAttributesCount(), + ) + if err != nil { + return fmt.Errorf("error occured while inserting event: %w", err) + } + } + + for l := 0; l < span.Links().Len(); l++ { + link := span.Links().At(l) + + attrs, err := pcommonMapAsJSON(link.Attributes()) + if err != nil { + return fmt.Errorf("failed to marshal link attributes as json: %w", err) + } + + stmt, err := tx.PrepareContext(ctx, insertLinkQ) + if err != nil { + return fmt.Errorf("failed to prepare link insert query: %w", err) + } + + linkidraw := [8]byte(link.SpanID()) + linkidbs := linkidraw[:] + linktraceraw := [16]byte(link.TraceID()) + linetracebs := linktraceraw[:] + + _, err = stmt.ExecContext(ctx, + spanidbs, + linkidbs, + linetracebs, + link.TraceState().AsRaw(), + attrs, + link.DroppedAttributesCount(), + ) + if err != nil { + return fmt.Errorf("error occured while inserting link: %w", err) + } + } + } + } + } + + return nil +} + +func pcommonMapAsJSON(m pcommon.Map) ([]byte, error) { + return json.Marshal(m.AsRaw()) } diff --git a/sqlite_exporter_test.go b/sqlite_exporter_test.go new file mode 100644 index 0000000..1bfff66 --- /dev/null +++ b/sqlite_exporter_test.go @@ -0,0 +1,257 @@ +// Copyright 2023 William Perron. All rights reserved. MIT License. +package sqliteexporter + +import ( + "context" + "database/sql" + "reflect" + "testing" + "time" + + _ "github.com/mattn/go-sqlite3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/ptrace" +) + +func Test_ExporterExportSpan(t *testing.T) { + ctx := context.Background() + now := time.Now() + + // manually build the exporter so we can inspect the database + db, err := sql.Open("sqlite3", ":memory:") + require.NoError(t, err) + + tx, _ := db.Begin() + + _, err = tx.Exec(initScript) + require.NoError(t, err) + + err = tx.Commit() + require.NoError(t, err) + + ex := sqliteExporter{db: db} + + // build the trace + testTrace := ptrace.NewTraces() + rs := testTrace.ResourceSpans().AppendEmpty() + r := rs.Resource() + r.Attributes().PutStr("service.name", "test-service") + ss := rs.ScopeSpans().AppendEmpty() + + // define a first server span + span1 := ss.Spans().AppendEmpty() + span1.SetTraceID(pcommon.TraceID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}) + span1.SetSpanID(pcommon.SpanID{0xee, 0xbc, 0x00, 0x00, 0x00, 0x00, 0xab, 0x01}) + ts := span1.TraceState() + ts.FromRaw("rojo=00f067aa0ba902b7") + span1.SetName("span1") + span1.SetKind(ptrace.SpanKindServer) + span1.SetStartTimestamp(pcommon.NewTimestampFromTime(now.Add(-5 * time.Millisecond))) + span1.SetEndTimestamp(pcommon.NewTimestampFromTime(now)) + span1.Status().SetCode(ptrace.StatusCodeOk) + span1.Status().SetMessage(ptrace.StatusCodeOk.String()) + span1.Attributes().PutStr("http.method", "GET") + span1.Attributes().PutInt("http.status_code", 200) + span1event1 := span1.Events().AppendEmpty() + span1event1.SetTimestamp(pcommon.NewTimestampFromTime(now.Add(-3 * time.Millisecond))) + span1event1.SetName("this_happened") + span1event1.Attributes().PutStr("value", "example") + span1event2 := span1.Events().AppendEmpty() + span1event2.SetTimestamp(pcommon.NewTimestampFromTime(now.Add(-4 * time.Millisecond))) + span1event2.SetName("that_happened") + span1event2.Attributes().PutStr("value", "example") + + // define an internal child span for span1 + span2 := ss.Spans().AppendEmpty() + span2.SetTraceID(pcommon.TraceID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}) + span2.SetSpanID(pcommon.SpanID{0xee, 0xbc, 0x00, 0x00, 0x00, 0x00, 0xab, 0x02}) + span2.SetParentSpanID(pcommon.SpanID{0xee, 0xbc, 0x00, 0x00, 0x00, 0x00, 0xab, 0x01}) + span2.SetName("span2") + span2.SetKind(ptrace.SpanKindInternal) + span2.SetStartTimestamp(pcommon.NewTimestampFromTime(now.Add(-4 * time.Millisecond))) + span2.SetEndTimestamp(pcommon.NewTimestampFromTime(now.Add(-1 * time.Millisecond))) + span2.Status().SetCode(ptrace.StatusCodeOk) + span2.Status().SetMessage(ptrace.StatusCodeOk.String()) + span2.Attributes().PutStr("custom.key", "custom-value") + span2event1 := span2.Events().AppendEmpty() + span2event1.SetTimestamp(pcommon.NewTimestampFromTime(now.Add(-3 * time.Millisecond))) + span2event1.SetName("this_happened") + span2event1.Attributes().PutStr("value", "example") + span2event2 := span2.Events().AppendEmpty() + span2event2.SetTimestamp(pcommon.NewTimestampFromTime(now.Add(-4 * time.Millisecond))) + span2event2.SetName("that_happened") + span2event2.Attributes().PutStr("value", "example") + span2link1 := span2.Links().AppendEmpty() + span2link1.SetSpanID(span1.SpanID()) + span2link1.SetTraceID(span1.TraceID()) + span2link1.Attributes().PutStr("relation", "follows_from") + + err = ex.ConsumeTraces(ctx, testTrace) + require.Nil(t, err) + + row := db.QueryRow("select count(1) from spans;") + require.NoError(t, err) + + var total int + err = row.Scan(&total) + require.NoError(t, err) + assert.Equal(t, 2, total) + + rows, err := db.Query(`select + span_id, + trace_id, + parent_span_id, + tracestate, + __service_name, + __duration, + name, + kind, + start_time, + end_time, + status_code, + status_description, + attributes, + dropped_attributes_count, + resource_attributes, + resource_dropped_attributes_count + from spans order by name asc;`) + require.NoError(t, err) + + ok := rows.Next() + require.True(t, ok) + + var rspanId, rtraceId, rparentSpanId sql.RawBytes + var tracestate, serviceName, name, kind, statusDescription, attributes, resourceAttributes string + var duration, startTime, endTime, statusCode, droppedAttributesCount, resourceDroppedAttributesCount int + err = rows.Scan( + &rspanId, + &rtraceId, + &rparentSpanId, + &tracestate, + &serviceName, + &duration, + &name, + &kind, + &startTime, + &endTime, + &statusCode, + &statusDescription, + &attributes, + &droppedAttributesCount, + &resourceAttributes, + &resourceDroppedAttributesCount, + ) + require.NoError(t, err) + assert.Equal(t, sql.RawBytes([]byte{0xee, 0xbc, 0x00, 0x00, 0x00, 0x00, 0xab, 0x01}), rspanId) + assert.Equal(t, sql.RawBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}), rtraceId) + assert.Nil(t, rparentSpanId) + assert.Equal(t, "rojo=00f067aa0ba902b7", tracestate) + assert.Equal(t, "test-service", serviceName) + assert.Equal(t, 5000, duration) + assert.Equal(t, "span1", name) + assert.Equal(t, ptrace.SpanKindServer.String(), kind) + assert.Equal(t, now.Add(-5*time.Millisecond).Unix(), int64(startTime)) + assert.Equal(t, now.Unix(), int64(endTime)) + assert.Equal(t, int(ptrace.StatusCodeOk), statusCode) + assert.Equal(t, ptrace.StatusCodeOk.String(), statusDescription) + assert.Equal(t, "{\"http.method\":\"GET\",\"http.status_code\":200}", attributes) + assert.Equal(t, 0, droppedAttributesCount) + assert.Equal(t, "{\"service.name\":\"test-service\"}", resourceAttributes) + assert.Equal(t, 0, resourceDroppedAttributesCount) + + ok = rows.Next() + require.True(t, ok) + + err = rows.Scan( + &rspanId, + &rtraceId, + &rparentSpanId, + &tracestate, + &serviceName, + &duration, + &name, + &kind, + &startTime, + &endTime, + &statusCode, + &statusDescription, + &attributes, + &droppedAttributesCount, + &resourceAttributes, + &resourceDroppedAttributesCount, + ) + require.NoError(t, err) + assert.Equal(t, sql.RawBytes([]byte{0xee, 0xbc, 0x00, 0x00, 0x00, 0x00, 0xab, 0x02}), rspanId) + assert.Equal(t, sql.RawBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}), rtraceId) + assert.Equal(t, sql.RawBytes([]byte{0xee, 0xbc, 0x00, 0x00, 0x00, 0x00, 0xab, 0x01}), rparentSpanId) + assert.Equal(t, "", tracestate) + assert.Equal(t, "test-service", serviceName) + assert.Equal(t, 3000, duration) + assert.Equal(t, "span2", name) + assert.Equal(t, ptrace.SpanKindInternal.String(), kind) + assert.Equal(t, now.Add(-4*time.Millisecond).Unix(), int64(startTime)) + assert.Equal(t, now.Add(-1*time.Millisecond).Unix(), int64(endTime)) + assert.Equal(t, int(ptrace.StatusCodeOk), statusCode) + assert.Equal(t, ptrace.StatusCodeOk.String(), statusDescription) + assert.Equal(t, "{\"custom.key\":\"custom-value\"}", attributes) + assert.Equal(t, 0, droppedAttributesCount) + assert.Equal(t, "{\"service.name\":\"test-service\"}", resourceAttributes) + assert.Equal(t, 0, resourceDroppedAttributesCount) + + assert.False(t, rows.Next()) + + row = db.QueryRow("select count(1) from events;") + require.NoError(t, err) + + err = row.Scan(&total) + require.NoError(t, err) + assert.Equal(t, 4, total) + + row = db.QueryRow("select count(1) from links;") + require.NoError(t, err) + + err = row.Scan(&total) + require.NoError(t, err) + assert.Equal(t, 1, total) +} + +func Test_pcommonMapAsJSON(t *testing.T) { + tests := []struct { + name string + args pcommon.Map + want []byte + wantErr bool + }{ + { + name: "empty", + args: pcommon.NewMap(), + want: []byte("{}"), + wantErr: false, + }, + { + name: "simple", + args: func() pcommon.Map { + m := pcommon.NewMap() + m.PutStr("http.method", "GET") + m.PutInt("http.status_code", 200) + return m + }(), + want: []byte("{\"http.method\":\"GET\",\"http.status_code\":200}"), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := pcommonMapAsJSON(tt.args) + if (err != nil) != tt.wantErr { + t.Errorf("pcommonMapAsJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("pcommonMapAsJSON() = %v, want %v", string(got), string(tt.want)) + } + }) + } +}