feat(telemetry): add OpenTelemetry tracing with optional OTLP export to Tempo
This commit is contained in:
parent
3f1638c98b
commit
fd9f55faff
9 changed files with 601 additions and 2 deletions
27
docker/tempo/docker-compose.yaml
Normal file
27
docker/tempo/docker-compose.yaml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
services:
|
||||
tempo:
|
||||
image: grafana/tempo:latest
|
||||
command: ["-config.file=/etc/tempo.yaml"]
|
||||
volumes:
|
||||
- ./tempo.yaml:/etc/tempo.yaml:ro
|
||||
- tempo-data:/var/tempo
|
||||
ports:
|
||||
- "4317:4317" # OTLP gRPC
|
||||
- "4318:4318" # OTLP HTTP
|
||||
- "3200:3200" # Tempo query API
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
environment:
|
||||
- GF_AUTH_ANONYMOUS_ENABLED=true
|
||||
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
|
||||
- GF_AUTH_DISABLE_LOGIN_FORM=true
|
||||
volumes:
|
||||
- ./grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml:ro
|
||||
ports:
|
||||
- "9715:3000" # Grafana UI (project port convention)
|
||||
depends_on:
|
||||
- tempo
|
||||
|
||||
volumes:
|
||||
tempo-data:
|
||||
11
docker/tempo/grafana-datasources.yaml
Normal file
11
docker/tempo/grafana-datasources.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Tempo
|
||||
type: tempo
|
||||
access: proxy
|
||||
url: http://tempo:3200
|
||||
isDefault: true
|
||||
jsonData:
|
||||
tracesToLogsV2:
|
||||
datasourceUid: ''
|
||||
19
docker/tempo/tempo.yaml
Normal file
19
docker/tempo/tempo.yaml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
server:
|
||||
http_listen_port: 3200
|
||||
|
||||
distributor:
|
||||
receivers:
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
endpoint: 0.0.0.0:4317
|
||||
http:
|
||||
endpoint: 0.0.0.0:4318
|
||||
|
||||
storage:
|
||||
trace:
|
||||
backend: local
|
||||
local:
|
||||
path: /var/tempo/traces
|
||||
wal:
|
||||
path: /var/tempo/wal
|
||||
369
v2/Cargo.lock
generated
369
v2/Cargo.lock
generated
|
|
@ -152,6 +152,17 @@ version = "0.7.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atk"
|
||||
version = "0.18.2"
|
||||
|
|
@ -297,6 +308,9 @@ dependencies = [
|
|||
"futures-util",
|
||||
"log",
|
||||
"notify",
|
||||
"opentelemetry",
|
||||
"opentelemetry-otlp",
|
||||
"opentelemetry_sdk",
|
||||
"rfd",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
|
|
@ -309,6 +323,9 @@ dependencies = [
|
|||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tracing",
|
||||
"tracing-opentelemetry",
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
|
@ -935,6 +952,12 @@ version = "1.0.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "embed-resource"
|
||||
version = "3.0.6"
|
||||
|
|
@ -1194,6 +1217,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1973,6 +1997,15 @@ version = "1.70.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
|
|
@ -2259,6 +2292,15 @@ dependencies = [
|
|||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.10"
|
||||
|
|
@ -2447,6 +2489,15 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.0"
|
||||
|
|
@ -2685,6 +2736,86 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "236e667b670a5cdf90c258f5a55794ec5ac5027e960c224bff8367a59e1e6426"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"js-sys",
|
||||
"pin-project-lite",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-http"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8863faf2910030d139fb48715ad5ff2f35029fc5f244f6d5f689ddcf4d26253"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"http",
|
||||
"opentelemetry",
|
||||
"reqwest 0.12.28",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-otlp"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bef114c6d41bea83d6dc60eb41720eedd0261a67af57b66dd2b84ac46c01d91"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures-core",
|
||||
"http",
|
||||
"opentelemetry",
|
||||
"opentelemetry-http",
|
||||
"opentelemetry-proto",
|
||||
"opentelemetry_sdk",
|
||||
"prost",
|
||||
"reqwest 0.12.28",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-proto"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f8870d3024727e99212eb3bb1762ec16e255e3e6f58eeb3dc8db1aa226746d"
|
||||
dependencies = [
|
||||
"opentelemetry",
|
||||
"opentelemetry_sdk",
|
||||
"prost",
|
||||
"tonic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry_sdk"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84dfad6042089c7fc1f6118b7040dc2eb4ab520abbf410b79dc481032af39570"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures-channel",
|
||||
"futures-executor",
|
||||
"futures-util",
|
||||
"glob",
|
||||
"opentelemetry",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
|
|
@ -2893,6 +3024,26 @@ dependencies = [
|
|||
"siphasher 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
|
|
@ -3087,6 +3238,29 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptr_meta"
|
||||
version = "0.1.4"
|
||||
|
|
@ -3328,6 +3502,40 @@ dependencies = [
|
|||
"bytecheck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.13.2"
|
||||
|
|
@ -3565,6 +3773,12 @@ version = "1.0.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
|
|
@ -3792,6 +4006,18 @@ dependencies = [
|
|||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.17.0"
|
||||
|
|
@ -3919,6 +4145,15 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_library"
|
||||
version = "0.1.9"
|
||||
|
|
@ -4256,7 +4491,7 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
|
|
@ -4435,7 +4670,7 @@ dependencies = [
|
|||
"minisign-verify",
|
||||
"osakit",
|
||||
"percent-encoding",
|
||||
"reqwest",
|
||||
"reqwest 0.13.2",
|
||||
"rustls",
|
||||
"semver",
|
||||
"serde",
|
||||
|
|
@ -4625,6 +4860,15 @@ dependencies = [
|
|||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.47"
|
||||
|
|
@ -4731,6 +4975,17 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.21.0"
|
||||
|
|
@ -4863,6 +5118,27 @@ version = "1.0.6+spec-1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"tokio-stream",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.3"
|
||||
|
|
@ -4915,9 +5191,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
|
|
@ -4925,6 +5213,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-opentelemetry"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "721f2d2569dce9f3dfbbddee5906941e953bfcdf736a62da3377f5751650cc36"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"opentelemetry",
|
||||
"opentelemetry_sdk",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-serde"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5113,6 +5462,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "value-bag"
|
||||
version = "1.12.0"
|
||||
|
|
@ -5322,6 +5677,16 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webkit2gtk"
|
||||
version = "2.0.2"
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@ uuid = { version = "1", features = ["v4"] }
|
|||
tokio-tungstenite = { version = "0.21", features = ["native-tls"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
futures-util = "0.3"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
||||
opentelemetry = "0.28"
|
||||
opentelemetry_sdk = { version = "0.28", features = ["rt-tokio"] }
|
||||
opentelemetry-otlp = { version = "0.28", features = ["http-proto", "reqwest-client"] }
|
||||
tracing-opentelemetry = "0.29"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ mod pty;
|
|||
mod remote;
|
||||
mod sidecar;
|
||||
mod session;
|
||||
mod telemetry;
|
||||
mod watcher;
|
||||
|
||||
use ctx::CtxDb;
|
||||
|
|
@ -25,11 +26,13 @@ struct AppState {
|
|||
file_watcher: Arc<FileWatcherManager>,
|
||||
ctx_db: Arc<CtxDb>,
|
||||
remote_manager: Arc<RemoteManager>,
|
||||
_telemetry: telemetry::TelemetryGuard,
|
||||
}
|
||||
|
||||
// --- PTY commands ---
|
||||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip(state), fields(shell = ?options.shell))]
|
||||
fn pty_spawn(
|
||||
state: State<'_, AppState>,
|
||||
options: PtyOptions,
|
||||
|
|
@ -53,6 +56,7 @@ fn pty_resize(
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip(state))]
|
||||
fn pty_kill(state: State<'_, AppState>, id: String) -> Result<(), String> {
|
||||
state.pty_manager.kill(&id)
|
||||
}
|
||||
|
|
@ -60,6 +64,7 @@ fn pty_kill(state: State<'_, AppState>, id: String) -> Result<(), String> {
|
|||
// --- Agent/sidecar commands ---
|
||||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip(state, options), fields(session_id = %options.session_id))]
|
||||
fn agent_query(
|
||||
state: State<'_, AppState>,
|
||||
options: AgentQueryOptions,
|
||||
|
|
@ -68,6 +73,7 @@ fn agent_query(
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip(state))]
|
||||
fn agent_stop(state: State<'_, AppState>, session_id: String) -> Result<(), String> {
|
||||
state.sidecar_manager.stop_session(&session_id)
|
||||
}
|
||||
|
|
@ -78,6 +84,7 @@ fn agent_ready(state: State<'_, AppState>) -> bool {
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip(state))]
|
||||
fn agent_restart(state: State<'_, AppState>) -> Result<(), String> {
|
||||
state.sidecar_manager.restart()
|
||||
}
|
||||
|
|
@ -470,6 +477,19 @@ fn cli_get_group() -> Option<String> {
|
|||
None
|
||||
}
|
||||
|
||||
// --- Frontend telemetry bridge ---
|
||||
|
||||
#[tauri::command]
|
||||
fn frontend_log(level: String, message: String, context: Option<serde_json::Value>) {
|
||||
match level.as_str() {
|
||||
"error" => tracing::error!(source = "frontend", ?context, "{message}"),
|
||||
"warn" => tracing::warn!(source = "frontend", ?context, "{message}"),
|
||||
"info" => tracing::info!(source = "frontend", ?context, "{message}"),
|
||||
"debug" => tracing::debug!(source = "frontend", ?context, "{message}"),
|
||||
_ => tracing::trace!(source = "frontend", ?context, "{message}"),
|
||||
}
|
||||
}
|
||||
|
||||
// --- Remote machine commands ---
|
||||
|
||||
#[tauri::command]
|
||||
|
|
@ -488,26 +508,31 @@ async fn remote_remove(state: State<'_, AppState>, machine_id: String) -> Result
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip(app, state))]
|
||||
async fn remote_connect(app: tauri::AppHandle, state: State<'_, AppState>, machine_id: String) -> Result<(), String> {
|
||||
state.remote_manager.connect(&app, &machine_id).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip(state))]
|
||||
async fn remote_disconnect(state: State<'_, AppState>, machine_id: String) -> Result<(), String> {
|
||||
state.remote_manager.disconnect(&machine_id).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip(state, options), fields(session_id = %options.session_id))]
|
||||
async fn remote_agent_query(state: State<'_, AppState>, machine_id: String, options: AgentQueryOptions) -> Result<(), String> {
|
||||
state.remote_manager.agent_query(&machine_id, &options).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip(state))]
|
||||
async fn remote_agent_stop(state: State<'_, AppState>, machine_id: String, session_id: String) -> Result<(), String> {
|
||||
state.remote_manager.agent_stop(&machine_id, &session_id).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip(state), fields(shell = ?options.shell))]
|
||||
async fn remote_pty_spawn(state: State<'_, AppState>, machine_id: String, options: PtyOptions) -> Result<String, String> {
|
||||
state.remote_manager.pty_spawn(&machine_id, &options).await
|
||||
}
|
||||
|
|
@ -532,6 +557,9 @@ pub fn run() {
|
|||
// Force dark GTK theme for native dialogs (file chooser, etc.)
|
||||
std::env::set_var("GTK_THEME", "Adwaita:dark");
|
||||
|
||||
// Initialize tracing + optional OTLP export (before any tracing macros)
|
||||
let telemetry_guard = telemetry::init();
|
||||
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
pty_spawn,
|
||||
|
|
@ -588,6 +616,7 @@ pub fn run() {
|
|||
project_agent_state_load,
|
||||
cli_get_group,
|
||||
pick_directory,
|
||||
frontend_log,
|
||||
])
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
|
|
@ -652,6 +681,7 @@ pub fn run() {
|
|||
file_watcher,
|
||||
ctx_db,
|
||||
remote_manager,
|
||||
_telemetry: telemetry_guard,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
101
v2/src-tauri/src/telemetry.rs
Normal file
101
v2/src-tauri/src/telemetry.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// OpenTelemetry telemetry — tracing spans + OTLP export to Tempo/Grafana
|
||||
//
|
||||
// Controlled by BTERMINAL_OTLP_ENDPOINT env var:
|
||||
// - Set (e.g. "http://localhost:4318") → export traces via OTLP/HTTP + console
|
||||
// - Absent → console-only (no network calls)
|
||||
|
||||
use opentelemetry::trace::TracerProvider;
|
||||
use opentelemetry_sdk::trace::SdkTracerProvider;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
|
||||
/// Holds the tracer provider and shuts it down on drop.
|
||||
/// Store this in Tauri's managed state so it lives for the app lifetime.
|
||||
pub struct TelemetryGuard {
|
||||
provider: Option<SdkTracerProvider>,
|
||||
}
|
||||
|
||||
impl Drop for TelemetryGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(provider) = self.provider.take() {
|
||||
if let Err(e) = provider.shutdown() {
|
||||
eprintln!("OTEL shutdown error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize tracing with optional OTLP export.
|
||||
/// Call once at app startup, before any tracing macros fire.
|
||||
pub fn init() -> TelemetryGuard {
|
||||
let filter = EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| EnvFilter::new("bterminal=info,bterminal_lib=info,bterminal_core=info"));
|
||||
|
||||
let fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.with_target(true)
|
||||
.compact();
|
||||
|
||||
match std::env::var("BTERMINAL_OTLP_ENDPOINT") {
|
||||
Ok(endpoint) if !endpoint.is_empty() => {
|
||||
match build_otlp_provider(&endpoint) {
|
||||
Ok(provider) => {
|
||||
let otel_layer = tracing_opentelemetry::layer()
|
||||
.with_tracer(provider.tracer("bterminal"));
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.with(fmt_layer)
|
||||
.with(otel_layer)
|
||||
.init();
|
||||
|
||||
log::info!("Telemetry: OTLP export enabled → {endpoint}");
|
||||
TelemetryGuard { provider: Some(provider) }
|
||||
}
|
||||
Err(e) => {
|
||||
// Fall back to console-only if OTLP setup fails
|
||||
tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.with(fmt_layer)
|
||||
.init();
|
||||
|
||||
log::warn!("Telemetry: OTLP setup failed ({e}), console-only fallback");
|
||||
TelemetryGuard { provider: None }
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.with(fmt_layer)
|
||||
.init();
|
||||
|
||||
log::info!("Telemetry: console-only (BTERMINAL_OTLP_ENDPOINT not set)");
|
||||
TelemetryGuard { provider: None }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_otlp_provider(endpoint: &str) -> Result<SdkTracerProvider, Box<dyn std::error::Error>> {
|
||||
use opentelemetry_otlp::{SpanExporter, WithExportConfig};
|
||||
use opentelemetry_sdk::trace::SdkTracerProvider;
|
||||
use opentelemetry_sdk::Resource;
|
||||
use opentelemetry::KeyValue;
|
||||
|
||||
let exporter = SpanExporter::builder()
|
||||
.with_http()
|
||||
.with_endpoint(endpoint)
|
||||
.build()?;
|
||||
|
||||
let resource = Resource::builder()
|
||||
.with_attributes([
|
||||
KeyValue::new("service.name", "bterminal"),
|
||||
KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
|
||||
])
|
||||
.build();
|
||||
|
||||
let provider = SdkTracerProvider::builder()
|
||||
.with_batch_exporter(exporter)
|
||||
.with_resource(resource)
|
||||
.build();
|
||||
|
||||
Ok(provider)
|
||||
}
|
||||
26
v2/src/lib/adapters/telemetry-bridge.ts
Normal file
26
v2/src/lib/adapters/telemetry-bridge.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Telemetry bridge — routes frontend events to Rust tracing via IPC
|
||||
// No browser OTEL SDK needed (WebKit2GTK incompatible)
|
||||
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
|
||||
|
||||
/** Emit a structured log event to the Rust tracing layer */
|
||||
export function telemetryLog(
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
context?: Record<string, unknown>,
|
||||
): void {
|
||||
invoke('frontend_log', { level, message, context: context ?? null }).catch(() => {
|
||||
// Swallow IPC errors — telemetry must never break the app
|
||||
});
|
||||
}
|
||||
|
||||
/** Convenience wrappers */
|
||||
export const tel = {
|
||||
error: (msg: string, ctx?: Record<string, unknown>) => telemetryLog('error', msg, ctx),
|
||||
warn: (msg: string, ctx?: Record<string, unknown>) => telemetryLog('warn', msg, ctx),
|
||||
info: (msg: string, ctx?: Record<string, unknown>) => telemetryLog('info', msg, ctx),
|
||||
debug: (msg: string, ctx?: Record<string, unknown>) => telemetryLog('debug', msg, ctx),
|
||||
trace: (msg: string, ctx?: Record<string, unknown>) => telemetryLog('trace', msg, ctx),
|
||||
};
|
||||
|
|
@ -22,6 +22,7 @@ import {
|
|||
saveAgentMessages,
|
||||
type AgentMessageRecord,
|
||||
} from './adapters/groups-bridge';
|
||||
import { tel } from './adapters/telemetry-bridge';
|
||||
|
||||
let unlistenMsg: (() => void) | null = null;
|
||||
let unlistenExit: (() => void) | null = null;
|
||||
|
|
@ -66,6 +67,7 @@ export async function startAgentDispatcher(): Promise<void> {
|
|||
switch (msg.type) {
|
||||
case 'agent_started':
|
||||
updateAgentStatus(sessionId, 'running');
|
||||
tel.info('agent_started', { sessionId });
|
||||
break;
|
||||
|
||||
case 'agent_event':
|
||||
|
|
@ -74,11 +76,13 @@ export async function startAgentDispatcher(): Promise<void> {
|
|||
|
||||
case 'agent_stopped':
|
||||
updateAgentStatus(sessionId, 'done');
|
||||
tel.info('agent_stopped', { sessionId });
|
||||
notify('success', `Agent ${sessionId.slice(0, 8)} completed`);
|
||||
break;
|
||||
|
||||
case 'agent_error':
|
||||
updateAgentStatus(sessionId, 'error', msg.message);
|
||||
tel.error('agent_error', { sessionId, error: msg.message });
|
||||
notify('error', `Agent error: ${msg.message ?? 'Unknown'}`);
|
||||
break;
|
||||
|
||||
|
|
@ -89,6 +93,7 @@ export async function startAgentDispatcher(): Promise<void> {
|
|||
|
||||
unlistenExit = await onSidecarExited(async () => {
|
||||
sidecarAlive = false;
|
||||
tel.error('sidecar_crashed', { restartAttempts });
|
||||
|
||||
// Guard against re-entrant exit handler (double-restart race)
|
||||
if (restarting) return;
|
||||
|
|
@ -176,6 +181,15 @@ function handleAgentEvent(sessionId: string, event: Record<string, unknown>): vo
|
|||
numTurns: cost.numTurns,
|
||||
durationMs: cost.durationMs,
|
||||
});
|
||||
tel.info('agent_cost', {
|
||||
sessionId,
|
||||
costUsd: cost.totalCostUsd,
|
||||
inputTokens: cost.inputTokens,
|
||||
outputTokens: cost.outputTokens,
|
||||
numTurns: cost.numTurns,
|
||||
durationMs: cost.durationMs,
|
||||
isError: cost.isError,
|
||||
});
|
||||
if (cost.isError) {
|
||||
updateAgentStatus(sessionId, 'error', cost.errors?.join('; '));
|
||||
notify('error', `Agent failed: ${cost.errors?.[0] ?? 'Unknown error'}`);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue