Openstatus www.openstatus.dev

๐Ÿ”ฅ Otel implemenation (#1181)

* ๐Ÿ”ฅ otel init

* ๐Ÿš€ otel

* improve otel

* ๐Ÿ”ฅ otel for tcp

* otel update

* Update apps/checker/pkg/otel/otel.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update apps/checker/pkg/otel/otel.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

authored by

Thibault Le Ouay
Copilot
and committed by
GitHub
881e2854 f04928cf

+378 -33
+10 -2
apps/checker/README.md
··· 7 7 ## How to run 8 8 9 9 ```bash 10 - go run *.go 10 + go run cmd/main.go 11 + ``` 12 + 13 + you can also set the env variable 14 + 15 + ```fish 16 + set CRON_SECRET YOLO 17 + set CLOUD_PROVIDER local 18 + set TINYBIRD_TOKEN random 11 19 ``` 12 20 13 21 ## How to build ··· 39 47 40 48 Use our docker image 41 49 42 - https://github.com/openstatusHQ/openstatus/pkgs/container/checker 50 + <https://github.com/openstatusHQ/openstatus/pkgs/container/checker>
+26 -8
apps/checker/go.mod
··· 1 1 module github.com/openstatushq/openstatus/apps/checker 2 2 3 - go 1.22 3 + go 1.22.0 4 + 5 + toolchain go1.23.5 4 6 5 7 require ( 6 - github.com/WilfredAlmeida/unkey-go v0.2.0 7 8 github.com/cenkalti/backoff/v4 v4.3.0 8 9 github.com/gin-gonic/gin v1.9.1 9 10 github.com/rs/zerolog v1.32.0 10 - github.com/stretchr/testify v1.9.0 11 + github.com/stretchr/testify v1.10.0 11 12 ) 12 13 13 14 require ( ··· 17 18 github.com/davecgh/go-spew v1.1.1 // indirect 18 19 github.com/gabriel-vasile/mimetype v1.4.3 // indirect 19 20 github.com/gin-contrib/sse v0.1.0 // indirect 21 + github.com/go-logr/logr v1.4.2 // indirect 22 + github.com/go-logr/stdr v1.2.2 // indirect 20 23 github.com/go-playground/locales v0.14.1 // indirect 21 24 github.com/go-playground/universal-translator v0.18.1 // indirect 22 25 github.com/go-playground/validator/v10 v10.19.0 // indirect 23 26 github.com/goccy/go-json v0.10.2 // indirect 24 27 github.com/google/go-cmp v0.6.0 // indirect 28 + github.com/google/uuid v1.6.0 // indirect 29 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect 25 30 github.com/json-iterator/go v1.1.12 // indirect 26 31 github.com/klauspost/cpuid/v2 v2.2.7 // indirect 27 32 github.com/leodido/go-urn v1.4.0 // indirect ··· 33 38 github.com/pmezard/go-difflib v1.0.0 // indirect 34 39 github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 35 40 github.com/ugorji/go/codec v1.2.12 // indirect 41 + go.opentelemetry.io/auto/sdk v1.1.0 // indirect 42 + go.opentelemetry.io/otel v1.34.0 // indirect 43 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect 44 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect 45 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.34.0 // indirect 46 + go.opentelemetry.io/otel/metric v1.34.0 // indirect 47 + go.opentelemetry.io/otel/sdk v1.34.0 // indirect 48 + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect 49 + go.opentelemetry.io/otel/trace v1.34.0 // indirect 50 + go.opentelemetry.io/proto/otlp v1.5.0 // indirect 36 51 golang.org/x/arch v0.7.0 // indirect 37 - golang.org/x/crypto v0.21.0 // indirect 38 - golang.org/x/net v0.22.0 // indirect 39 - golang.org/x/sys v0.18.0 // indirect 40 - golang.org/x/text v0.14.0 // indirect 41 - google.golang.org/protobuf v1.33.0 // indirect 52 + golang.org/x/crypto v0.32.0 // indirect 53 + golang.org/x/net v0.34.0 // indirect 54 + golang.org/x/sys v0.29.0 // indirect 55 + golang.org/x/text v0.21.0 // indirect 56 + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect 57 + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect 58 + google.golang.org/grpc v1.69.4 // indirect 59 + google.golang.org/protobuf v1.36.3 // indirect 42 60 gopkg.in/yaml.v3 v3.0.1 // indirect 43 61 )
+47 -4
apps/checker/go.sum
··· 1 - github.com/WilfredAlmeida/unkey-go v0.2.0 h1:WnPDbfrwgiI2uZQmFhSATY5Q1FU8NCGtQ+h1VjJWfoI= 2 - github.com/WilfredAlmeida/unkey-go v0.2.0/go.mod h1:KZZ52al64FQUnWx0Zf2fIFmZvznwUiHqIg0nak/BGMw= 3 1 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 4 2 github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= 5 3 github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= ··· 23 21 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 24 22 github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 25 23 github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 24 + github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 25 + github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 26 + github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 27 + github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 28 + github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 26 29 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 27 30 github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 28 31 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= ··· 37 40 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 38 41 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 39 42 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 40 - github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 41 - github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 43 + github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 44 + github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 45 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= 46 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= 42 47 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 43 48 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 44 49 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= ··· 78 83 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 79 84 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 80 85 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 86 + github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 87 + github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 81 88 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 82 89 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 83 90 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 84 91 github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 92 + go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 93 + go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 94 + go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= 95 + go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= 96 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= 97 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= 98 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= 99 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= 100 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.34.0 h1:czJDQwFrMbOr9Kk+BPo1y8WZIIFIK58SA1kykuVeiOU= 101 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.34.0/go.mod h1:lT7bmsxOe58Tq+JIOkTQMCGXdu47oA+VJKLZHbaBKbs= 102 + go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= 103 + go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= 104 + go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= 105 + go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= 106 + go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= 107 + go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= 108 + go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= 109 + go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= 110 + go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= 111 + go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= 85 112 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 86 113 golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= 87 114 golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 88 115 golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 89 116 golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 117 + golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= 118 + golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 90 119 golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= 91 120 golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 121 + golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= 122 + golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 92 123 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 93 124 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 94 125 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 95 126 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 96 127 golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 97 128 golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 129 + golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 130 + golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 98 131 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 99 132 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 133 + golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 134 + golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 135 + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= 136 + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= 137 + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= 138 + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= 139 + google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= 140 + google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 100 141 google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 101 142 google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 143 + google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= 144 + google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 102 145 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 103 146 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 104 147 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+5 -2
apps/checker/handlers/checker.go
··· 12 12 13 13 "github.com/openstatushq/openstatus/apps/checker" 14 14 "github.com/openstatushq/openstatus/apps/checker/pkg/assertions" 15 + otelOS "github.com/openstatushq/openstatus/apps/checker/pkg/otel" 15 16 "github.com/openstatushq/openstatus/apps/checker/request" 16 17 ) 17 18 ··· 82 83 assertionAsString = "" 83 84 } 84 85 85 - var trigger = "cron" 86 + trigger := "cron" 86 87 if req.Trigger != "" { 87 88 trigger = req.Trigger 88 89 } ··· 133 134 for _, a := range req.RawAssertions { 134 135 var assert request.Assertion 135 136 err = json.Unmarshal(a, &assert) 136 - 137 137 if err != nil { 138 138 // handle error 139 139 return fmt.Errorf("unable to unmarshal assertion: %w", err) ··· 306 306 CronTimestamp: req.CronTimestamp, 307 307 }) 308 308 } 309 + } 309 310 311 + if req.OtelConfig.Endpoint != "" { 312 + otelOS.RecordHTTPMetrics(ctx, req, result, h.Region) 310 313 } 311 314 312 315 returnData := c.Query("data")
+11 -17
apps/checker/handlers/tcp.go
··· 10 10 "github.com/cenkalti/backoff/v4" 11 11 "github.com/gin-gonic/gin" 12 12 "github.com/openstatushq/openstatus/apps/checker" 13 + otelOS "github.com/openstatushq/openstatus/apps/checker/pkg/otel" 13 14 "github.com/openstatushq/openstatus/apps/checker/request" 14 15 "github.com/rs/zerolog/log" 15 16 ) 16 - 17 - type TCPResponse struct { 18 - Region string `json:"region"` 19 - ErrorMessage string `json:"errorMessage"` 20 - JobType string `json:"jobType"` 21 - RequestId int64 `json:"requestId,omitempty"` 22 - WorkspaceID int64 `json:"workspaceId"` 23 - MonitorID int64 `json:"monitorId"` 24 - Timestamp int64 `json:"timestamp"` 25 - Latency int64 `json:"latency"` 26 - Timing checker.TCPResponseTiming `json:"timing"` 27 - Error uint8 `json:"error,omitempty"` 28 - } 29 17 30 18 // Only used for Tinybird. 31 19 type TCPData struct { ··· 97 85 98 86 var called int 99 87 100 - var response TCPResponse 88 + var response checker.TCPResponse 101 89 102 90 op := func() error { 103 91 called++ ··· 128 116 URI: req.URI, 129 117 } 130 118 131 - response = TCPResponse{ 119 + response = checker.TCPResponse{ 132 120 Timestamp: res.TCPStart, 133 121 Timing: checker.TCPResponseTiming{ 134 122 TCPStart: res.TCPStart, ··· 259 247 260 248 var called int 261 249 262 - var response TCPResponse 250 + var response checker.TCPResponse 263 251 264 252 op := func() error { 265 253 called++ ··· 270 258 return fmt.Errorf("unable to check tcp %s", err) 271 259 } 272 260 273 - response = TCPResponse{ 261 + response = checker.TCPResponse{ 274 262 Timestamp: timestamp, 275 263 Timing: checker.TCPResponseTiming{ 276 264 TCPStart: res.TCPStart, ··· 314 302 c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) 315 303 316 304 return 305 + } 306 + 307 + if req.OtelConfig.Endpoint != "" { 308 + 309 + otelOS.RecordTCPMetrics(ctx, req, response, region) 310 + 317 311 } 318 312 319 313 c.JSON(http.StatusOK, response)
+258
apps/checker/pkg/otel/otel.go
··· 1 + package otel 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "fmt" 7 + "time" 8 + 9 + "github.com/openstatushq/openstatus/apps/checker" 10 + "github.com/openstatushq/openstatus/apps/checker/request" 11 + "github.com/rs/zerolog/log" 12 + 13 + "go.opentelemetry.io/otel" 14 + "go.opentelemetry.io/otel/attribute" 15 + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" 16 + "go.opentelemetry.io/otel/metric" 17 + sdkMetrics "go.opentelemetry.io/otel/sdk/metric" 18 + "go.opentelemetry.io/otel/sdk/resource" 19 + 20 + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" 21 + ) 22 + 23 + func SetupOTelSDK( 24 + ctx context.Context, url string, probes string, headers map[string]string, 25 + ) (shutdown func(context.Context) error, err error) { 26 + var shutdownFuncs []func(context.Context) error 27 + 28 + shutdown = func(ctx context.Context) error { 29 + var err error 30 + 31 + for _, fn := range shutdownFuncs { 32 + err = errors.Join(err, fn(ctx)) 33 + } 34 + 35 + shutdownFuncs = nil 36 + 37 + return err 38 + } 39 + 40 + handleErr := func(inErr error) { 41 + err = errors.Join(inErr, shutdown(ctx)) 42 + } 43 + 44 + res, err := newResource() 45 + if err != nil { 46 + handleErr(err) 47 + 48 + return nil, err 49 + } 50 + 51 + meterProvider, err := newMeterProvider(ctx, res, url, headers) 52 + if err != nil { 53 + handleErr(err) 54 + 55 + return nil, err 56 + } 57 + 58 + shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown) 59 + 60 + otel.SetMeterProvider(meterProvider) 61 + 62 + return shutdown, nil 63 + } 64 + 65 + func newResource() (*resource.Resource, error) { 66 + return resource.Merge(resource.Default(), 67 + resource.NewWithAttributes(semconv.SchemaURL, 68 + semconv.ServiceName("openstatus-synthetic-check"), 69 + semconv.ServiceVersion("0.1.0"), 70 + )) 71 + } 72 + 73 + func newMeterProvider( 74 + ctx context.Context, 75 + res *resource.Resource, 76 + url string, 77 + headers map[string]string, 78 + ) (*sdkMetrics.MeterProvider, error) { 79 + 80 + grafanaExporter, err := otlpmetrichttp.New(ctx, 81 + otlpmetrichttp.WithEndpointURL(url), 82 + // otlpmetrichttp.WithInsecure(), 83 + otlpmetrichttp.WithHeaders(headers), 84 + ) 85 + if err != nil { 86 + return nil, err 87 + } 88 + 89 + meterProvider := sdkMetrics.NewMeterProvider( 90 + sdkMetrics.WithResource(res), 91 + 92 + sdkMetrics.WithReader(sdkMetrics.NewPeriodicReader(grafanaExporter, 93 + sdkMetrics.WithInterval(3*time.Second))), 94 + ) 95 + 96 + return meterProvider, nil 97 + } 98 + 99 + func RecordHTTPMetrics(ctx context.Context, req request.HttpCheckerRequest, result checker.Response, region string) { 100 + 101 + otelShutdown, err := SetupOTelSDK(ctx, req.OtelConfig.Endpoint, region, req.OtelConfig.Headers) 102 + 103 + if err != nil { 104 + log.Ctx(ctx).Error().Err(err).Msg("Error setting up otel") 105 + } 106 + 107 + defer func() { 108 + err = errors.Join(err, otelShutdown(ctx)) 109 + if err != nil { 110 + log.Ctx(ctx).Error().Err(err).Msg("Error sending the data") 111 + } 112 + }() 113 + 114 + meter := otel.Meter("OpenStatus") 115 + 116 + if result.Error != "" { 117 + att := metric.WithAttributes( 118 + attribute.String("openstatus.probes", region), 119 + attribute.String("openstatus.target", req.URL), 120 + semconv.HTTPResponseStatusCode(result.Status), 121 + ) 122 + statusError, err := meter.Int64Counter("openstatus.error", metric.WithDescription("Status of the check")) 123 + 124 + if err != nil { 125 + log.Ctx(ctx).Error().Err(err).Msg("Error setting up counter") 126 + } 127 + 128 + statusError.Add(ctx, (1), att) 129 + 130 + return 131 + } 132 + 133 + att := metric.WithAttributes( 134 + attribute.String("openstatus.probes", region), 135 + attribute.String("openstatus.target", req.URL), 136 + semconv.HTTPResponseStatusCode(result.Status), 137 + ) 138 + 139 + status, err := meter.Int64Counter("openstatus.status", metric.WithDescription("Status of the check")) 140 + 141 + if err != nil { 142 + log.Ctx(ctx).Error().Err(err).Msg("Error setting up conunter") 143 + } 144 + 145 + status.Add(ctx, 1, att) 146 + 147 + gauge, err := meter.Float64Gauge("openstatus.http.request.duration", 148 + metric.WithDescription("Duration of the check"), metric.WithUnit("ms")) 149 + 150 + if err != nil { 151 + fmt.Println("Error creating gauge", err) 152 + } 153 + 154 + gauge.Record(ctx, float64(result.Latency), att) 155 + 156 + gaugeDns, err := meter.Float64Gauge("openstatus.http.dns.duration", 157 + metric.WithDescription("Duration of the dns lookup"), metric.WithUnit("ms")) 158 + 159 + if err != nil { 160 + fmt.Println("Error creating gauge", err) 161 + } 162 + 163 + gaugeDns.Record(ctx, float64(result.Timing.DnsDone-result.Timing.DnsStart), att) 164 + 165 + gaugeConnect, err := meter.Float64Gauge("openstatus.http.connection.duration", 166 + metric.WithDescription("Duration of the connection"), metric.WithUnit("ms")) 167 + 168 + if err != nil { 169 + fmt.Println("Error creating gauge", err) 170 + } 171 + 172 + gaugeConnect.Record(ctx, float64(result.Timing.ConnectDone-result.Timing.ConnectStart), att) 173 + 174 + gaugeTLS, err := meter.Float64Gauge("openstatus.http.tls.duration", 175 + metric.WithDescription("Duration of the tls handshake"), metric.WithUnit("ms")) 176 + 177 + if err != nil { 178 + fmt.Println("Error creating gauge", err) 179 + } 180 + 181 + gaugeTLS.Record(ctx, float64(result.Timing.TlsHandshakeDone-result.Timing.TlsHandshakeStart), att) 182 + 183 + gaugeTTFB, err := meter.Float64Gauge("openstatus.http.ttfb.duration", 184 + metric.WithDescription("Duration of the ttfb"), metric.WithUnit("ms")) 185 + 186 + if err != nil { 187 + fmt.Println("Error creating gauge", err) 188 + } 189 + 190 + gaugeTTFB.Record(ctx, float64(result.Timing.FirstByteDone-result.Timing.FirstByteStart), att) 191 + 192 + gaugeTransfer, err := meter.Float64Gauge("openstatus.http.transfer.duration", 193 + metric.WithDescription("Duration of the transfer"), metric.WithUnit("ms")) 194 + 195 + if err != nil { 196 + fmt.Println("Error creating gauge", err) 197 + } 198 + 199 + gaugeTransfer.Record(ctx, float64(result.Timing.TransferDone-result.Timing.TransferStart), att) 200 + } 201 + 202 + func RecordTCPMetrics(ctx context.Context, req request.TCPCheckerRequest, result checker.TCPResponse, region string) { 203 + 204 + otelShutdown, err := SetupOTelSDK(ctx, req.OtelConfig.Endpoint, region, req.OtelConfig.Headers) 205 + 206 + if err != nil { 207 + log.Ctx(ctx).Error().Err(err).Msg("Error setting up otel") 208 + } 209 + 210 + defer func() { 211 + err = errors.Join(err, otelShutdown(ctx)) 212 + if err != nil { 213 + log.Ctx(ctx).Error().Err(err).Msg("Error sending the data") 214 + } 215 + }() 216 + 217 + meter := otel.Meter("OpenStatus") 218 + 219 + if result.Error == 1 { 220 + att := metric.WithAttributes( 221 + attribute.String("openstatus.probes", region), 222 + attribute.String("openstatus.target", req.URI), 223 + ) 224 + statusError, err := meter.Int64Counter("openstatus.error", metric.WithDescription("Status of the check")) 225 + 226 + if err != nil { 227 + log.Ctx(ctx).Error().Err(err).Msg("Error setting up counter") 228 + } 229 + 230 + statusError.Add(ctx, (1), att) 231 + 232 + return 233 + } 234 + 235 + att := metric.WithAttributes( 236 + attribute.String("openstatus.probes", region), 237 + attribute.String("openstatus.target", req.URI), 238 + ) 239 + 240 + gauge, err := meter.Float64Gauge("openstatus.tcp.request.duration", 241 + metric.WithDescription("Duration of the check"), metric.WithUnit("ms")) 242 + 243 + if err != nil { 244 + fmt.Println("Error creating gauge", err) 245 + } 246 + 247 + gauge.Record(ctx, float64(result.Latency), att) 248 + 249 + gaugeTCP, err := meter.Float64Gauge("openstatus.tcp.tcp.duration", 250 + metric.WithDescription("Duration of the dns lookup"), metric.WithUnit("ms")) 251 + 252 + if err != nil { 253 + fmt.Println("Error creating gauge", err) 254 + } 255 + 256 + gaugeTCP.Record(ctx, float64(result.Timing.TCPDone-result.Timing.TCPStart), att) 257 + 258 + }
+8
apps/checker/request/request.go
··· 61 61 CronTimestamp int64 `json:"cronTimestamp"` 62 62 Timeout int64 `json:"timeout"` 63 63 DegradedAfter int64 `json:"degradedAfter,omitempty"` 64 + OtelConfig struct { 65 + Endpoint string `json:"endpoint"` 66 + Headers map[string]string `json:"headers,omitempty"` 67 + } `json:"otelConfig,omitempty"` 64 68 } 65 69 66 70 type TCPCheckerRequest struct { ··· 74 78 CronTimestamp int64 `json:"cronTimestamp"` 75 79 Timeout int64 `json:"timeout"` 76 80 DegradedAfter int64 `json:"degradedAfter,omitempty"` 81 + OtelConfig struct { 82 + Endpoint string `json:"endpoint"` 83 + Headers map[string]string `json:"headers,omitempty"` 84 + } `json:"otelConfig,omitempty"` 77 85 } 78 86 79 87 type TCPRequest struct {
+13
apps/checker/tcp.go
··· 18 18 TCPDone int64 `json:"tcpDone"` 19 19 } 20 20 21 + type TCPResponse struct { 22 + Region string `json:"region"` 23 + ErrorMessage string `json:"errorMessage"` 24 + JobType string `json:"jobType"` 25 + RequestId int64 `json:"requestId,omitempty"` 26 + WorkspaceID int64 `json:"workspaceId"` 27 + MonitorID int64 `json:"monitorId"` 28 + Timestamp int64 `json:"timestamp"` 29 + Latency int64 `json:"latency"` 30 + Timing TCPResponseTiming `json:"timing"` 31 + Error uint8 `json:"error,omitempty"` 32 + } 33 + 21 34 func PingTcp(timeout int, url string) (TCPResponseTiming, error) { 22 35 start := time.Now().UTC().UnixMilli() 23 36 conn, err := net.DialTimeout("tcp", url, time.Duration(timeout)*time.Second)