feat(tracing): Start to implement tracing system
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
99bca634d2
commit
06f0722cdb
9
go.mod
9
go.mod
@ -8,12 +8,17 @@ require (
|
||||
github.com/juju/errors v1.0.0
|
||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
go.opentelemetry.io/otel v1.11.2
|
||||
go.opentelemetry.io/otel/sdk v1.11.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/jonboulle/clockwork v0.3.0 // indirect
|
||||
github.com/lestrrat-go/strftime v1.0.6 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.11.2 // indirect
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
|
||||
)
|
||||
|
20
go.sum
20
go.sum
@ -5,6 +5,12 @@ 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/gemnasium/logrus-graylog-hook/v3 v3.1.0 h1:SLtCnpI5ZZaz4l7RSatEhppB1BBhUEu+DqGANJzJdEA=
|
||||
github.com/gemnasium/logrus-graylog-hook/v3 v3.1.0/go.mod h1:wi1zWv9tIvyLSMLCAzgRP+YR24oLVQVBHfPPKjtht44=
|
||||
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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
|
||||
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/juju/errors v1.0.0 h1:yiq7kjCLll1BiaRuNY53MGI0+EQ3rF6GB+wvboZDefM=
|
||||
@ -30,13 +36,21 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0=
|
||||
go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI=
|
||||
go.opentelemetry.io/otel/sdk v1.11.2 h1:GF4JoaEx7iihdMFu30sOyRx52HDHOkl9xQ8SMqNXUiU=
|
||||
go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rqyLc3SyX9aU=
|
||||
go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0=
|
||||
go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
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=
|
||||
|
2
tracing/README.md
Normal file
2
tracing/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# Tracing Common
|
||||
Contain code that allow to interact with a tracing app such as jaeger / zipkin
|
167
tracing/config.go
Normal file
167
tracing/config.go
Normal file
@ -0,0 +1,167 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"git.dev.m-and-m.ovh/mderasse/gocommon/convert"
|
||||
"git.dev.m-and-m.ovh/mderasse/gocommon/tracing/exporter/jaeger"
|
||||
"git.dev.m-and-m.ovh/mderasse/gocommon/tracing/exporter/zipkin"
|
||||
)
|
||||
|
||||
// envPrefix is the prefix that will be used for any environnement variable used to configure the tracing system.
|
||||
const envPrefix = "TRACING_"
|
||||
|
||||
// CONFIG_FILE is the default file that will be searched to apply configuration from the filesystem.
|
||||
const defaultConfigFile = "tracing.yaml"
|
||||
|
||||
// SECRET_NAME is the default name of the secret that will be searched in vault.
|
||||
const defaultSecretName = "tracing"
|
||||
|
||||
// ConfigStruct represent the configuration of our tracing system.
|
||||
type ConfigStruct struct {
|
||||
Attributes map[string]string `yaml:"attributes"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Exporter ExporterName `yaml:"exporter"`
|
||||
ServiceName string `yaml:"service_name"`
|
||||
ServiceVersion string `yaml:"service_version"`
|
||||
ServiceInstanceID string `yaml:"service_instance_id"`
|
||||
JaegerConfig *jaeger.ConfigStruct `yaml:"jaeger_config"`
|
||||
ZipkinConfig *zipkin.ConfigStruct `yaml:"zipkin_config"`
|
||||
}
|
||||
|
||||
func newDefaultConfig() *ConfigStruct {
|
||||
return &ConfigStruct{
|
||||
Enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
func loadConfigFromVault(secret string) (*ConfigStruct, error) {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
// loadConfigFromFile will read the given file and return a config struct.
|
||||
func loadConfigFromFile(path string) (*ConfigStruct, error) {
|
||||
|
||||
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
//nolint:gosec // we did compute the file path
|
||||
f, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
var config ConfigStruct
|
||||
|
||||
if err := yaml.Unmarshal(f, &config); err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func loadConfig() (*ConfigStruct, error) {
|
||||
|
||||
c, err := loadConfigFromVault(defaultSecretName)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, err
|
||||
} else if err == nil {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
c, err = loadConfigFromFile(defaultConfigFile)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, err
|
||||
} else if err == nil {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
return newDefaultConfig(), nil
|
||||
}
|
||||
|
||||
// applyEnv will retrieve info from environment variable and overide those got by config / vault.
|
||||
// validity of the value is not checked here and will be check in a IsValid method.
|
||||
func (c *ConfigStruct) applyEnv() error {
|
||||
|
||||
if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "ENABLED")); v != "" {
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return errors.NewNotValid(err, fmt.Sprintf("Invalid %s%s environment variable. Should be a boolean", envPrefix, "ENABLED"))
|
||||
}
|
||||
c.Enabled = b
|
||||
}
|
||||
|
||||
if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "SERVICE_NAME")); v != "" {
|
||||
c.ServiceName = v
|
||||
}
|
||||
|
||||
if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "SERVICE_VERSION")); v != "" {
|
||||
c.ServiceVersion = v
|
||||
}
|
||||
|
||||
if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "SERVICE_INSTANCE_ID")); v != "" {
|
||||
c.ServiceInstanceID = v
|
||||
}
|
||||
|
||||
if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "EXPORTER")); v != "" {
|
||||
c.Exporter = ExporterName(v)
|
||||
}
|
||||
|
||||
// Attributes
|
||||
if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "ATTRIBUTES")); v != "" {
|
||||
attributeParts := strings.Split(v, ",")
|
||||
for _, ap := range attributeParts {
|
||||
attributeKV := strings.SplitN(ap, ":", 1)
|
||||
if len(attributeKV) != 2 {
|
||||
return errors.NotValidf(fmt.Sprintf("Invalid attribute %s in environment variable. Should be a key1:value1,key2:value2 format", ap))
|
||||
}
|
||||
c.Attributes[attributeKV[0]] = attributeKV[1]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid will check a config struct.
|
||||
func (c *ConfigStruct) IsValid() error {
|
||||
|
||||
if c.Enabled {
|
||||
if c.ServiceName == "" {
|
||||
return errors.NotValidf("ServiceName is empty")
|
||||
}
|
||||
|
||||
if !c.Exporter.IsValid() {
|
||||
return errors.NotValidf("Invalid Exporter %s. Allowed values: %s", c.Exporter.String(), strings.Join(convert.StringerSliceToStringSlice(GetListExporterName()), ", "))
|
||||
}
|
||||
|
||||
switch c.Exporter {
|
||||
case ExporterName_JAEGER:
|
||||
if c.JaegerConfig == nil {
|
||||
return errors.NotValidf("jaeger configuration is empty")
|
||||
}
|
||||
err := c.JaegerConfig.IsValid()
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
case ExporterName_ZIPKIN:
|
||||
if c.ZipkinConfig == nil {
|
||||
return errors.NotValidf("zipkin configuration is empty")
|
||||
}
|
||||
err := c.ZipkinConfig.IsValid()
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
default:
|
||||
return errors.NotValidf("Exporter not handled")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
34
tracing/enum.go
Normal file
34
tracing/enum.go
Normal file
@ -0,0 +1,34 @@
|
||||
package tracing
|
||||
|
||||
// Exporter Name Enum
|
||||
|
||||
// ExporterName is an exporter that will receive the tracing.
|
||||
type ExporterName string
|
||||
|
||||
//nolint:exported // keeping the enum simple and readable.
|
||||
const (
|
||||
ExporterName_JAEGER ExporterName = "JAEGER"
|
||||
ExporterName_ZIPKIN ExporterName = "ZIPKIN"
|
||||
)
|
||||
|
||||
// IsValid check if the gaven ExporterName is part of the list of handled exporter name.
|
||||
func (e ExporterName) IsValid() bool {
|
||||
for _, v := range GetListExporterName() {
|
||||
if e == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e ExporterName) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// GetListExporterName return a the list of possible ExporterName.
|
||||
func GetListExporterName() []ExporterName {
|
||||
return []ExporterName{
|
||||
ExporterName_JAEGER,
|
||||
ExporterName_ZIPKIN,
|
||||
}
|
||||
}
|
23
tracing/exporter/jaeger/config.go
Normal file
23
tracing/exporter/jaeger/config.go
Normal file
@ -0,0 +1,23 @@
|
||||
package jaeger
|
||||
|
||||
import (
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
// ConfigStruct is the configuration for Jaeger Provider.
|
||||
type ConfigStruct struct {
|
||||
URL string `yaml:"url"`
|
||||
}
|
||||
|
||||
// IsValid will check that the Jaeger configuration is valid.
|
||||
func (c *ConfigStruct) IsValid() error {
|
||||
|
||||
if c.URL == "" {
|
||||
return errors.NotValidf("URL is empty in Jaeger configuration")
|
||||
} else if isValid := govalidator.IsURL(c.URL); !isValid {
|
||||
return errors.NotValidf("URL is invalid in Jaeger configuration")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
23
tracing/exporter/zipkin/config.go
Normal file
23
tracing/exporter/zipkin/config.go
Normal file
@ -0,0 +1,23 @@
|
||||
package zipkin
|
||||
|
||||
import (
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
// ConfigStruct is the configuration for Zipkin Provider.
|
||||
type ConfigStruct struct {
|
||||
URL string `yaml:"url"`
|
||||
}
|
||||
|
||||
// IsValid will check that the Zipkin configuration is valid.
|
||||
func (c *ConfigStruct) IsValid() error {
|
||||
|
||||
if c.URL == "" {
|
||||
return errors.NotValidf("URL is empty in Zipkin configuration")
|
||||
} else if isValid := govalidator.IsURL(c.URL); !isValid {
|
||||
return errors.NotValidf("URL is invalid in Zipkin configuration")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
81
tracing/tracing.go
Normal file
81
tracing/tracing.go
Normal file
@ -0,0 +1,81 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"github.com/juju/errors"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
|
||||
)
|
||||
|
||||
// Init will try to initialize tracing by trying to retrieve config from multiple source.
|
||||
func Init() (*sdktrace.TracerProvider, error) {
|
||||
|
||||
// loading configuration
|
||||
c, err := loadConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
return initFromSource(c)
|
||||
}
|
||||
|
||||
// InitFromCustomVaultSecret will initialize tracing with a vault secret.
|
||||
func InitFromCustomVaultSecret(secret string) (*sdktrace.TracerProvider, error) {
|
||||
|
||||
c, err := loadConfigFromVault(secret)
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
return initFromSource(c)
|
||||
}
|
||||
|
||||
// InitFromCustomFile will initialize tracing with a config file.
|
||||
func InitFromCustomFile(path string) (*sdktrace.TracerProvider, error) {
|
||||
|
||||
c, err := loadConfigFromFile(path)
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
return initFromSource(c)
|
||||
}
|
||||
|
||||
func initFromSource(c *ConfigStruct) (*sdktrace.TracerProvider, error) {
|
||||
err := c.applyEnv()
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
return InitFromCustomConfig(c)
|
||||
}
|
||||
|
||||
// InitFromCustomConfig will initialize tracing from a gaven config.
|
||||
func InitFromCustomConfig(c *ConfigStruct) (*sdktrace.TracerProvider, error) {
|
||||
|
||||
err := c.IsValid()
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
// Ensure default SDK resources and the required service name are set.
|
||||
r, err := resource.Merge(
|
||||
resource.Default(),
|
||||
resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.ServiceNameKey.String("myService"),
|
||||
semconv.ServiceVersionKey.String("1.0.0"),
|
||||
semconv.ServiceInstanceIDKey.String("abcdef12345"),
|
||||
),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sdktrace.NewTracerProvider(
|
||||
sdktrace.WithResource(r),
|
||||
), nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user