diff --git a/.docker/observability/README.md b/.docker/observability/README.md new file mode 100644 index 000000000..a84f7592d --- /dev/null +++ b/.docker/observability/README.md @@ -0,0 +1,101 @@ +# Observability + +This directory contains the observability stack for the application. The stack is composed of the following components: + +- Prometheus v3.2.1 +- Grafana 11.6.0 +- Loki 3.4.2 +- Jaeger 2.4.0 +- Otel Collector 0.120.0 #0.121.0 remove loki + +## Prometheus + +Prometheus is a monitoring and alerting toolkit. It scrapes metrics from instrumented jobs, either directly or via an +intermediary push gateway for short-lived jobs. It stores all scraped samples locally and runs rules over this data to +either aggregate and record new time series from existing data or generate alerts. Grafana or other API consumers can be +used to visualize the collected data. + +## Grafana + +Grafana is a multi-platform open-source analytics and interactive visualization web application. It provides charts, +graphs, and alerts for the web when connected to supported data sources. + +## Loki + +Loki is a horizontally-scalable, highly-available, multi-tenant log aggregation system inspired by Prometheus. It is +designed to be very cost-effective and easy to operate. It does not index the contents of the logs, but rather a set of +labels for each log stream. + +## Jaeger + +Jaeger is a distributed tracing system released as open source by Uber Technologies. It is used for monitoring and +troubleshooting microservices-based distributed systems, including: + +- Distributed context propagation +- Distributed transaction monitoring +- Root cause analysis +- Service dependency analysis +- Performance / latency optimization + +## Otel Collector + +The OpenTelemetry Collector offers a vendor-agnostic implementation on how to receive, process, and export telemetry +data. It removes the need to run, operate, and maintain multiple agents/collectors in order to support open-source +observability data formats (e.g. Jaeger, Prometheus, etc.) sending to one or more open-source or commercial back-ends. + +## How to use + +To deploy the observability stack, run the following command: + +```bash +docker-compose -f docker-compose.yml -f docker-compose.override.yml up -d +``` + +To access the Grafana dashboard, navigate to `http://localhost:3000` in your browser. The default username and password +are `admin` and `admin`, respectively. + +To access the Jaeger dashboard, navigate to `http://localhost:16686` in your browser. + +To access the Prometheus dashboard, navigate to `http://localhost:9090` in your browser. + +## How to stop + +To stop the observability stack, run the following command: + +```bash +docker-compose -f docker-compose.yml -f docker-compose.override.yml down +``` + +## How to remove data + +To remove the data generated by the observability stack, run the following command: + +```bash +docker-compose -f docker-compose.yml -f docker-compose.override.yml down -v +``` + +## How to configure + +To configure the observability stack, modify the `docker-compose.override.yml` file. The file contains the following + +```yaml +services: + prometheus: + environment: + - PROMETHEUS_CONFIG_FILE=/etc/prometheus/prometheus.yml + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + + grafana: + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + volumes: + - ./grafana/provisioning:/etc/grafana/provisioning +``` + +The `prometheus` service mounts the `prometheus.yml` file to `/etc/prometheus/prometheus.yml`. The `grafana` service +mounts the `grafana/provisioning` directory to `/etc/grafana/provisioning`. You can modify these files to configure the +observability stack. + + + diff --git a/.docker/observability/docker-compose.yml b/.docker/observability/docker-compose.yml new file mode 100644 index 000000000..f6714bb26 --- /dev/null +++ b/.docker/observability/docker-compose.yml @@ -0,0 +1,65 @@ +services: + otel-collector: + image: otel/opentelemetry-collector-contrib:0.120.0 + environment: + - TZ=Asia/Shanghai + volumes: + - ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml + ports: + - 1888:1888 + - 8888:8888 + - 8889:8889 + - 13133:13133 + - 4317:4317 + - 4318:4318 + - 55679:55679 + networks: + - otel-network + jaeger: + image: jaegertracing/jaeger:2.4.0 + environment: + - TZ=Asia/Shanghai + ports: + - "16686:16686" + - "14317:4317" + - "14318:4318" + networks: + - otel-network + prometheus: + image: prom/prometheus:v3.2.1 + environment: + - TZ=Asia/Shanghai + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + ports: + - "9090:9090" + networks: + - otel-network + loki: + image: grafana/loki:3.4.2 + environment: + - TZ=Asia/Shanghai + volumes: + - ./loki-config.yaml:/etc/loki/local-config.yaml + ports: + - "3100:3100" + command: -config.file=/etc/loki/local-config.yaml + networks: + - otel-network + grafana: + image: grafana/grafana:11.6.0 + ports: + - "3000:3000" # Web UI + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + - TZ=Asia/Shanghai + networks: + - otel-network + + +networks: + otel-network: + driver: bridge + name: "network_otel_config" + driver_opts: + com.docker.network.enable_ipv6: "true" diff --git a/.docker/observability/jaeger-config.yaml b/.docker/observability/jaeger-config.yaml new file mode 100644 index 000000000..0df35f836 --- /dev/null +++ b/.docker/observability/jaeger-config.yaml @@ -0,0 +1,87 @@ +service: + extensions: [ jaeger_storage, jaeger_query, remote_sampling, healthcheckv2 ] + pipelines: + traces: + receivers: [ otlp, jaeger, zipkin ] + processors: [ batch, adaptive_sampling ] + exporters: [ jaeger_storage_exporter ] + telemetry: + resource: + service.name: jaeger + metrics: + level: detailed + readers: + - pull: + exporter: + prometheus: + host: 0.0.0.0 + port: 8888 + logs: + level: debug + # TODO Initialize telemetry tracer once OTEL released new feature. + # https://github.com/open-telemetry/opentelemetry-collector/issues/10663 + +extensions: + healthcheckv2: + use_v2: true + http: + + # pprof: + # endpoint: 0.0.0.0:1777 + # zpages: + # endpoint: 0.0.0.0:55679 + + jaeger_query: + storage: + traces: some_store + traces_archive: another_store + ui: + config_file: ./cmd/jaeger/config-ui.json + # The maximum duration that is considered for clock skew adjustments. + # Defaults to 0 seconds, which means it's disabled. + max_clock_skew_adjust: 0s + + jaeger_storage: + backends: + some_store: + memory: + max_traces: 100000 + another_store: + memory: + max_traces: 100000 + + remote_sampling: + # You can either use file or adaptive sampling strategy in remote_sampling + # file: + # path: ./cmd/jaeger/sampling-strategies.json + adaptive: + sampling_store: some_store + initial_sampling_probability: 0.1 + http: + grpc: + +receivers: + otlp: + protocols: + grpc: + http: + + jaeger: + protocols: + grpc: + thrift_binary: + thrift_compact: + thrift_http: + + zipkin: + +processors: + batch: + # Adaptive Sampling Processor is required to support adaptive sampling. + # It expects remote_sampling extension with `adaptive:` config to be enabled. + adaptive_sampling: + +exporters: + jaeger_storage_exporter: + trace_storage: some_store + diff --git a/.docker/observability/loki-config.yaml b/.docker/observability/loki-config.yaml new file mode 100644 index 000000000..4aff8772a --- /dev/null +++ b/.docker/observability/loki-config.yaml @@ -0,0 +1,63 @@ +auth_enabled: false + +server: + http_listen_port: 3100 + grpc_listen_port: 9096 + log_level: debug + grpc_server_max_concurrent_streams: 1000 + +common: + instance_addr: 127.0.0.1 + path_prefix: /tmp/loki + storage: + filesystem: + chunks_directory: /tmp/loki/chunks + rules_directory: /tmp/loki/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + +query_range: + results_cache: + cache: + embedded_cache: + enabled: true + max_size_mb: 100 + +limits_config: + metric_aggregation_enabled: true + +schema_config: + configs: + - from: 2020-10-24 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + +pattern_ingester: + enabled: true + metric_aggregation: + loki_address: localhost:3100 + +ruler: + alertmanager_url: http://localhost:9093 + +frontend: + encoding: protobuf + +# By default, Loki will send anonymous, but uniquely-identifiable usage and configuration +# analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/ +# +# Statistics help us better understand how Loki is used, and they show us performance +# levels for most users. This helps us prioritize features and documentation. +# For more information on what's sent, look at +# https://github.com/grafana/loki/blob/main/pkg/analytics/stats.go +# Refer to the buildReport method to see what goes into a report. +# +# If you would like to disable reporting, uncomment the following lines: +#analytics: +# reporting_enabled: false diff --git a/.docker/observability/otel-collector-config.yaml b/.docker/observability/otel-collector-config.yaml new file mode 100644 index 000000000..24d67dc3c --- /dev/null +++ b/.docker/observability/otel-collector-config.yaml @@ -0,0 +1,57 @@ +receivers: + otlp: + protocols: + grpc: # OTLP gRPC 接收器 + endpoint: 0.0.0.0:4317 + http: # OTLP HTTP 接收器 + endpoint: 0.0.0.0:4318 + +processors: + batch: # 批处理处理器,提升吞吐量 + timeout: 5s + send_batch_size: 1000 + memory_limiter: + check_interval: 1s + limit_mib: 512 + +exporters: + otlp/traces: # OTLP 导出器,用于跟踪数据 + endpoint: "jaeger:4317" # Jaeger 的 OTLP gRPC 端点 + tls: + insecure: true # 开发环境禁用 TLS,生产环境需配置证书 + prometheus: # Prometheus 导出器,用于指标数据 + endpoint: "0.0.0.0:8889" # Prometheus 刮取端点 + namespace: "rustfs" # 指标前缀 + send_timestamps: true # 发送时间戳 + # enable_open_metrics: true + loki: # Loki 导出器,用于日志数据 + # endpoint: "http://loki:3100/otlp/v1/logs" + endpoint: "http://loki:3100/loki/api/v1/push" + tls: + insecure: true +extensions: + health_check: + pprof: + zpages: +service: + extensions: [ health_check, pprof, zpages ] # 启用扩展 + pipelines: + traces: + receivers: [ otlp ] + processors: [ memory_limiter,batch ] + exporters: [ otlp/traces ] + metrics: + receivers: [ otlp ] + processors: [ batch ] + exporters: [ prometheus ] + logs: + receivers: [ otlp ] + processors: [ batch ] + exporters: [ loki ] + telemetry: + logs: + level: "info" # Collector 日志级别 + metrics: + address: "0.0.0.0:8888" # Collector 自身指标暴露 + + diff --git a/.docker/observability/prometheus.yml b/.docker/observability/prometheus.yml new file mode 100644 index 000000000..8d3a031ef --- /dev/null +++ b/.docker/observability/prometheus.yml @@ -0,0 +1,11 @@ +global: + scrape_interval: 5s # 刮取间隔 + +scrape_configs: + - job_name: 'otel-collector' + static_configs: + - targets: ['otel-collector:8888'] # 从 Collector 刮取指标 + - job_name: 'otel-metrics' + static_configs: + - targets: ['otel-collector:8889'] # 应用指标 + diff --git a/.gitignore b/.gitignore index e9172ab10..8513695bf 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,6 @@ rustfs/static/* vendor cli/rustfs-gui/embedded-rustfs/rustfs -.log config/obs.toml -config/certs/* \ No newline at end of file +*.log +config/certs/* diff --git a/Cargo.lock b/Cargo.lock index 1db4030ef..a10f342d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,9 +174,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "api" @@ -218,6 +218,12 @@ dependencies = [ "password-hash", ] +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + [[package]] name = "arrayref" version = "0.3.9" @@ -232,9 +238,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "arrow" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc208515aa0151028e464cc94a692156e945ce5126abd3537bb7fd6ba2143ed1" +checksum = "b5ec52ba94edeed950e4a41f75d35376df196e8cb04437f7280a5aa49f20f796" dependencies = [ "arrow-arith", "arrow-array", @@ -253,9 +259,9 @@ dependencies = [ [[package]] name = "arrow-arith" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e07e726e2b3f7816a85c6a45b6ec118eeeabf0b2a8c208122ad949437181f49a" +checksum = "8fc766fdacaf804cb10c7c70580254fcdb5d55cdfda2bc57b02baf5223a3af9e" dependencies = [ "arrow-array", "arrow-buffer", @@ -267,9 +273,9 @@ dependencies = [ [[package]] name = "arrow-array" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2262eba4f16c78496adfd559a29fe4b24df6088efc9985a873d58e92be022d5" +checksum = "a12fcdb3f1d03f69d3ec26ac67645a8fe3f878d77b5ebb0b15d64a116c212985" dependencies = [ "ahash 0.8.11", "arrow-buffer", @@ -284,9 +290,9 @@ dependencies = [ [[package]] name = "arrow-buffer" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e899dade2c3b7f5642eb8366cfd898958bcca099cde6dfea543c7e8d3ad88d4" +checksum = "263f4801ff1839ef53ebd06f99a56cecd1dbaf314ec893d93168e2e860e0291c" dependencies = [ "bytes", "half", @@ -295,9 +301,9 @@ dependencies = [ [[package]] name = "arrow-cast" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4103d88c5b441525ed4ac23153be7458494c2b0c9a11115848fdb9b81f6f886a" +checksum = "ede6175fbc039dfc946a61c1b6d42fd682fcecf5ab5d148fbe7667705798cac9" dependencies = [ "arrow-array", "arrow-buffer", @@ -305,7 +311,7 @@ dependencies = [ "arrow-schema", "arrow-select", "atoi", - "base64", + "base64 0.22.1", "chrono", "comfy-table", "half", @@ -316,9 +322,9 @@ dependencies = [ [[package]] name = "arrow-csv" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d3cb0914486a3cae19a5cad2598e44e225d53157926d0ada03c20521191a65" +checksum = "1644877d8bc9a0ef022d9153dc29375c2bda244c39aec05a91d0e87ccf77995f" dependencies = [ "arrow-array", "arrow-cast", @@ -332,9 +338,9 @@ dependencies = [ [[package]] name = "arrow-data" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a329fb064477c9ec5f0870d2f5130966f91055c7c5bce2b3a084f116bc28c3b" +checksum = "61cfdd7d99b4ff618f167e548b2411e5dd2c98c0ddebedd7df433d34c20a4429" dependencies = [ "arrow-buffer", "arrow-schema", @@ -344,9 +350,9 @@ dependencies = [ [[package]] name = "arrow-ipc" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddecdeab02491b1ce88885986e25002a3da34dd349f682c7cfe67bab7cc17b86" +checksum = "62ff528658b521e33905334723b795ee56b393dbe9cf76c8b1f64b648c65a60c" dependencies = [ "arrow-array", "arrow-buffer", @@ -358,9 +364,9 @@ dependencies = [ [[package]] name = "arrow-json" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b9340013413eb84868682ace00a1098c81a5ebc96d279f7ebf9a4cac3c0fd" +checksum = "0ee5b4ca98a7fb2efb9ab3309a5d1c88b5116997ff93f3147efdc1062a6158e9" dependencies = [ "arrow-array", "arrow-buffer", @@ -369,18 +375,20 @@ dependencies = [ "arrow-schema", "chrono", "half", - "indexmap 2.7.1", + "indexmap 2.8.0", "lexical-core", + "memchr", "num", "serde", "serde_json", + "simdutf8", ] [[package]] name = "arrow-ord" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f841bfcc1997ef6ac48ee0305c4dfceb1f7c786fe31e67c1186edf775e1f1160" +checksum = "f0a3334a743bd2a1479dbc635540617a3923b4b2f6870f37357339e6b5363c21" dependencies = [ "arrow-array", "arrow-buffer", @@ -391,9 +399,9 @@ dependencies = [ [[package]] name = "arrow-row" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eeb55b0a0a83851aa01f2ca5ee5648f607e8506ba6802577afdda9d75cdedcd" +checksum = "8d1d7a7291d2c5107e92140f75257a99343956871f3d3ab33a7b41532f79cb68" dependencies = [ "arrow-array", "arrow-buffer", @@ -404,15 +412,15 @@ dependencies = [ [[package]] name = "arrow-schema" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85934a9d0261e0fa5d4e2a5295107d743b543a6e0484a835d4b8db2da15306f9" +checksum = "39cfaf5e440be44db5413b75b72c2a87c1f8f0627117d110264048f2969b99e9" [[package]] name = "arrow-select" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2932aece2d0c869dd2125feb9bd1709ef5c445daa3838ac4112dcfa0fda52c" +checksum = "69efcd706420e52cd44f5c4358d279801993846d1c2a8e52111853d61d55a619" dependencies = [ "ahash 0.8.11", "arrow-array", @@ -424,9 +432,9 @@ dependencies = [ [[package]] name = "arrow-string" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "912e38bd6a7a7714c1d9b61df80315685553b7455e8a6045c27531d8ecd5b458" +checksum = "a21546b337ab304a32cfc0770f671db7411787586b45b78b4593ae78e64e2b03" dependencies = [ "arrow-array", "arrow-buffer", @@ -458,22 +466,19 @@ dependencies = [ [[package]] name = "ashpd" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c39d707614dbcc6bed00015539f488d8e3fe3e66ed60961efc0c90f4b380b3" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" dependencies = [ "enumflags2", "futures-channel", "futures-util", - "rand 0.8.5", + "rand 0.9.0", "raw-window-handle 0.6.2", "serde", "serde_repr", "tokio", "url", - "wayland-backend", - "wayland-client", - "wayland-protocols", "zbus 5.5.0", ] @@ -531,7 +536,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 0.38.44", "slab", "tracing", "windows-sys 0.59.0", @@ -563,7 +568,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix", + "rustix 0.38.44", "tracing", ] @@ -575,7 +580,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -590,7 +595,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix", + "rustix 0.38.44", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -615,7 +620,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -626,13 +631,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -681,9 +686,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.12.6" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dabb68eb3a7aa08b46fddfd59a3d55c978243557a90ab804769f7e20e67d2b01" +checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" dependencies = [ "aws-lc-sys", "zeroize", @@ -691,9 +696,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77926887776171ced7d662120a75998e444d3750c951abfe07f90da130514b1f" +checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f" dependencies = [ "bindgen", "cc", @@ -717,7 +722,7 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", - "itoa 1.0.14", + "itoa 1.0.15", "matchit 0.7.3", "memchr", "mime", @@ -782,9 +787,9 @@ dependencies = [ [[package]] name = "backon" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fef586913a57ff189f25c9b3d034356a5bf6b3fa9a7f067588fe1698ba1f5d" +checksum = "970d91570c01a8a5959b36ad7dd1c30642df24b6b3068710066f6809f7033bb7" dependencies = [ "fastrand", "gloo-timers", @@ -806,6 +811,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -824,15 +835,15 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] name = "bigdecimal" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" +checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" dependencies = [ "autocfg", "libm", @@ -860,7 +871,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.98", + "syn 2.0.100", "which", ] @@ -890,9 +901,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675f87afced0413c9bb02843499dbbd3882a237645883f71a2b59644a6d2f753" +checksum = "34a796731680be7931955498a16a10b2270c7762963d5d570fdbfe02dcbf314f" dependencies = [ "arrayref", "arrayvec", @@ -918,9 +929,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.11.0-rc.3" +version = "0.11.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd016a0ddc7cb13661bf5576073ce07330a693f8608a1320b4e20561cc12cdc" +checksum = "a229bfd78e4827c91b9b95784f69492c1b77c1ab75a45a8a037b139215086f94" dependencies = [ "hybrid-array", ] @@ -991,15 +1002,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bytesize" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2c12f985c78475a6b8d629afd0c360260ef34cfef52efccdcfd31972f81c2e" +checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" [[package]] name = "bytestring" @@ -1056,9 +1067,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" dependencies = [ "jobserver", "libc", @@ -1139,9 +1150,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1149,14 +1160,14 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "chrono-tz" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6ac4f2c0bf0f44e9161aec9675e1050aa4a530663c4a9e37e108fa948bca9f" +checksum = "efdce149c370f133a071ca8ef6ea340b7b88748ab0810097a9e2976eaa34b4f3" dependencies = [ "chrono", "chrono-tz-build", @@ -1165,9 +1176,9 @@ dependencies = [ [[package]] name = "chrono-tz-build" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" +checksum = "8f10f8c9340e31fc120ff885fcdb54a0b48e474bbd77cab557f0c30a3e569402" dependencies = [ "parse-zoneinfo", "phf_codegen 0.11.3", @@ -1224,9 +1235,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.31" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" dependencies = [ "clap_builder", "clap_derive", @@ -1234,9 +1245,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" dependencies = [ "anstream", "anstyle", @@ -1246,14 +1257,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1378,6 +1389,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595aae20e65c3be792d05818e8c63025294ac3cb7e200f11459063a352a6ef80" +dependencies = [ + "async-trait", + "convert_case 0.6.0", + "json5", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "winnow 0.7.4", + "yaml-rust2", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1432,7 +1462,7 @@ checksum = "04382d0d9df7434af6b1b49ea1a026ef39df1b0738b1cc373368cf175354f6eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1452,7 +1482,7 @@ checksum = "f0d1c4c3cb85e5856b34e829af0035d7154f8c2889b15bbf43c8a6c6786dcab5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1632,7 +1662,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "test-case", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", ] @@ -1649,13 +1679,11 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0-rc.1" +version = "0.2.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b8ce8218c97789f16356e7896b3714f26c2ee1079b79c0b7ae7064bb9089fa" +checksum = "170d71b5b14dec99db7739f6fc7d6ec2db80b78c3acb77db48392ccc3d8a9ea0" dependencies = [ - "getrandom 0.2.15", "hybrid-array", - "rand_core 0.6.4", ] [[package]] @@ -1682,7 +1710,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1692,7 +1720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", - "itoa 1.0.14", + "itoa 1.0.15", "ryu", "serde", ] @@ -1717,9 +1745,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -1727,27 +1755,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1785,9 +1813,9 @@ checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" [[package]] name = "datafusion" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46b879c1aa3a85ecbfa376704f0fe4bfebae1a44a5d35faa4466bf85469b6a0e" +checksum = "914e6f9525599579abbd90b0f7a55afcaaaa40350b9e9ed52563f126dfe45fd3" dependencies = [ "arrow", "arrow-ipc", @@ -1836,9 +1864,9 @@ dependencies = [ [[package]] name = "datafusion-catalog" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e42f516243fe30137f2b7d5712611286baf8d1d758a46157bada7c35fdf38df" +checksum = "998a6549e6ee4ee3980e05590b2960446a56b343ea30199ef38acd0e0b9036e2" dependencies = [ "arrow", "async-trait", @@ -1856,9 +1884,9 @@ dependencies = [ [[package]] name = "datafusion-catalog-listing" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e495290c231d617f0a940860a885cb2f4c3efe46c1983c30d3fa12faf1ccb208" +checksum = "a5ac10096a5b3c0d8a227176c0e543606860842e943594ccddb45cf42a526e43" dependencies = [ "arrow", "async-trait", @@ -1878,17 +1906,17 @@ dependencies = [ [[package]] name = "datafusion-common" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af67ddc82e1c8e6843c326ca13aa20e5420cce9f886b4e1ee39ea43defae3145" +checksum = "1f53d7ec508e1b3f68bd301cee3f649834fad51eff9240d898a4b2614cfd0a7a" dependencies = [ "ahash 0.8.11", "arrow", "arrow-ipc", - "base64", + "base64 0.22.1", "half", "hashbrown 0.14.5", - "indexmap 2.7.1", + "indexmap 2.8.0", "libc", "log", "object_store", @@ -1902,9 +1930,9 @@ dependencies = [ [[package]] name = "datafusion-common-runtime" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ee9403a2ec39183437825d232f556a5dee89f13f6fd78f8c7f8f999489e4ca" +checksum = "e0fcf41523b22e14cc349b01526e8b9f59206653037f2949a4adbfde5f8cb668" dependencies = [ "log", "tokio", @@ -1912,9 +1940,9 @@ dependencies = [ [[package]] name = "datafusion-datasource" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8c8b7568b638dd309bcc1cdeb66776f233b110d44bdc6fd67ef1919f9ec9803" +checksum = "cf7f37ad8b6e88b46c7eeab3236147d32ea64b823544f498455a8d9042839c92" dependencies = [ "arrow", "async-compression", @@ -1946,15 +1974,15 @@ dependencies = [ [[package]] name = "datafusion-doc" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8612c81304578a2e2b82d31caf8173312cb086a7a23a23556b9fff3ac7c18221" +checksum = "7db7a0239fd060f359dc56c6e7db726abaa92babaed2fb2e91c3a8b2fff8b256" [[package]] name = "datafusion-execution" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3591e6d4900e57bad7f861f14f5c763f716da76553b0d037ec91c192c876f09c" +checksum = "0938f9e5b6bc5782be4111cdfb70c02b7b5451bf34fd57e4de062a7f7c4e31f1" dependencies = [ "arrow", "dashmap 6.1.0", @@ -1971,9 +1999,9 @@ dependencies = [ [[package]] name = "datafusion-expr" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033d0f6198d177f50a7721d80db141af15dd12f45ad6dce34e2cdbb6538e39d" +checksum = "b36c28b00b00019a8695ad7f1a53ee1673487b90322ecbd604e2cf32894eb14f" dependencies = [ "arrow", "chrono", @@ -1983,7 +2011,7 @@ dependencies = [ "datafusion-functions-aggregate-common", "datafusion-functions-window-common", "datafusion-physical-expr-common", - "indexmap 2.7.1", + "indexmap 2.8.0", "paste", "recursive", "serde_json", @@ -1992,26 +2020,26 @@ dependencies = [ [[package]] name = "datafusion-expr-common" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56def48a7dfb9f92aa18e18dfdffaca79b5383f03c59bb0107959c1698634557" +checksum = "18f0a851a436c5a2139189eb4617a54e6a9ccb9edc96c4b3c83b3bb7c58b950e" dependencies = [ "arrow", "datafusion-common", - "indexmap 2.7.1", + "indexmap 2.8.0", "itertools 0.14.0", "paste", ] [[package]] name = "datafusion-functions" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a79b703b42b0aac97485b84c6810c78114b0974a75a33514840ba0bbe0de38f" +checksum = "e3196e37d7b65469fb79fee4f05e5bb58a456831035f9a38aa5919aeb3298d40" dependencies = [ "arrow", "arrow-buffer", - "base64", + "base64 0.22.1", "blake2", "blake3", "chrono", @@ -2034,9 +2062,9 @@ dependencies = [ [[package]] name = "datafusion-functions-aggregate" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdad20375e85365ed262b5583955c308840efc6ff9271ff463cf86789adfb686" +checksum = "adfc2d074d5ee4d9354fdcc9283d5b2b9037849237ddecb8942a29144b77ca05" dependencies = [ "ahash 0.8.11", "arrow", @@ -2055,9 +2083,9 @@ dependencies = [ [[package]] name = "datafusion-functions-aggregate-common" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff73249ee3cdc81ad04317d3b4231fc02a8c03a3a1b4b13953244e6443f6b498" +checksum = "1cbceba0f98d921309a9121b702bcd49289d383684cccabf9a92cda1602f3bbb" dependencies = [ "ahash 0.8.11", "arrow", @@ -2068,9 +2096,9 @@ dependencies = [ [[package]] name = "datafusion-functions-nested" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20dcd70c58f17b7ce937866e43c75293a3250aadc1db830ad6d502967aaffb40" +checksum = "170e27ce4baa27113ddf5f77f1a7ec484b0dbeda0c7abbd4bad3fc609c8ab71a" dependencies = [ "arrow", "arrow-ord", @@ -2089,9 +2117,9 @@ dependencies = [ [[package]] name = "datafusion-functions-table" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac12628c3e43461118e95d5772f729e1cc39db883d8ee52e4b80038b0f614bbf" +checksum = "7d3a06a7f0817ded87b026a437e7e51de7f59d48173b0a4e803aa896a7bd6bb5" dependencies = [ "arrow", "async-trait", @@ -2105,9 +2133,9 @@ dependencies = [ [[package]] name = "datafusion-functions-window" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03eb449555c7cc03bb61d43d90edef70d070d34bc4a0d8f7e358d157232f3220" +checksum = "d6c608b66496a1e05e3d196131eb9bebea579eed1f59e88d962baf3dda853bc6" dependencies = [ "datafusion-common", "datafusion-doc", @@ -2122,9 +2150,9 @@ dependencies = [ [[package]] name = "datafusion-functions-window-common" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a0c7606e568ee6a15d33a2532eb0d18e7769bb88af55f6b70be4db9fd937d18" +checksum = "da2f9d83348957b4ad0cd87b5cb9445f2651863a36592fe5484d43b49a5f8d82" dependencies = [ "datafusion-common", "datafusion-physical-expr-common", @@ -2132,27 +2160,27 @@ dependencies = [ [[package]] name = "datafusion-macros" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64030e805d3d257e3012e4378500d4ac90b1ebacd03f1110e8ec927b77f09486" +checksum = "4800e1ff7ecf8f310887e9b54c9c444b8e215ccbc7b21c2f244cfae373b1ece7" dependencies = [ "datafusion-expr", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "datafusion-optimizer" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6af7bdae7565aa7a4cb1deb7fe18d89c63c5d93b5203b473ca1dbe02a1cd3d" +checksum = "971c51c54cd309001376fae752fb15a6b41750b6d1552345c46afbfb6458801b" dependencies = [ "arrow", "chrono", "datafusion-common", "datafusion-expr", "datafusion-physical-expr", - "indexmap 2.7.1", + "indexmap 2.8.0", "itertools 0.14.0", "log", "recursive", @@ -2162,9 +2190,9 @@ dependencies = [ [[package]] name = "datafusion-physical-expr" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f68601feda90c255c9023a881e833efca9d7539bab0565ac1355b0249326e91" +checksum = "e1447c2c6bc8674a16be4786b4abf528c302803fafa186aa6275692570e64d85" dependencies = [ "ahash 0.8.11", "arrow", @@ -2175,7 +2203,7 @@ dependencies = [ "datafusion-physical-expr-common", "half", "hashbrown 0.14.5", - "indexmap 2.7.1", + "indexmap 2.8.0", "itertools 0.14.0", "log", "paste", @@ -2184,9 +2212,9 @@ dependencies = [ [[package]] name = "datafusion-physical-expr-common" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c1a08b00d340ca3bc1cd2f094ecaeaf6f099a2980e11255976660fa0409182" +checksum = "69f8c25dcd069073a75b3d2840a79d0f81e64bdd2c05f2d3d18939afb36a7dcb" dependencies = [ "ahash 0.8.11", "arrow", @@ -2198,9 +2226,9 @@ dependencies = [ [[package]] name = "datafusion-physical-optimizer" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd34f3438cf9629ea0e3425027582334fb6671a05ee43671ca3c47896b75dda" +checksum = "68da5266b5b9847c11d1b3404ee96b1d423814e1973e1ad3789131e5ec912763" dependencies = [ "arrow", "datafusion-common", @@ -2217,9 +2245,9 @@ dependencies = [ [[package]] name = "datafusion-physical-plan" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7624484ada341d30ef465eae61f760e779f080c621bbc3dc0335a75fa78e8dec" +checksum = "88cc160df00e413e370b3b259c8ea7bfbebc134d32de16325950e9e923846b7f" dependencies = [ "ahash 0.8.11", "arrow", @@ -2237,7 +2265,7 @@ dependencies = [ "futures", "half", "hashbrown 0.14.5", - "indexmap 2.7.1", + "indexmap 2.8.0", "itertools 0.14.0", "log", "parking_lot 0.12.3", @@ -2247,15 +2275,15 @@ dependencies = [ [[package]] name = "datafusion-sql" -version = "46.0.0" +version = "46.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e717736a394ed92d9dcf2d74439c655474dd39aa65a064a6bae697b6d20e5fe" +checksum = "325a212b67b677c0eb91447bf9a11b630f9fc4f62d8e5d145bf859f5a6b29e64" dependencies = [ "arrow", "bigdecimal", "datafusion-common", "datafusion-expr", - "indexmap 2.7.1", + "indexmap 2.8.0", "log", "recursive", "regex", @@ -2288,9 +2316,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" dependencies = [ "powerfmt", "serde", @@ -2314,7 +2342,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2324,7 +2352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2337,7 +2365,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2353,13 +2381,13 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0-pre.9" +version = "0.11.0-pre.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2e3d6615d99707295a9673e889bf363a04b2a466bd320c65a72536f7577379" +checksum = "6c478574b20020306f98d61c8ca3322d762e1ff08117422ac6106438605ea516" dependencies = [ - "block-buffer 0.11.0-rc.3", + "block-buffer 0.11.0-rc.4", "const-oid", - "crypto-common 0.2.0-rc.1", + "crypto-common 0.2.0-rc.2", "subtle", ] @@ -2439,7 +2467,7 @@ dependencies = [ "dioxus-rsx", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2458,7 +2486,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b0cca3e7a10a4a3df37ea52c4cc7a53e5c9233489e03ee3f2829471fc3099a" dependencies = [ "async-trait", - "base64", + "base64 0.22.1", "cocoa 0.25.0", "core-foundation 0.9.4", "dioxus-cli-config", @@ -2552,7 +2580,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe99b48a1348eec385b5c4bd3e80fd863b0d3b47257d34e2ddc58754dec5d128" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "ciborium", "dioxus-desktop", @@ -2633,7 +2661,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2726,7 +2754,7 @@ dependencies = [ "proc-macro2", "quote", "slab", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2738,7 +2766,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2799,7 +2827,7 @@ dependencies = [ "proc-macro2", "quote", "server_fn_macro", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2850,6 +2878,18 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dispatch2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0" +dependencies = [ + "bitflags 2.9.0", + "block2 0.6.0", + "libc", + "objc2 0.6.0", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -2858,16 +2898,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading 0.8.6", + "syn 2.0.100", ] [[package]] @@ -2890,14 +2921,17 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] -name = "downcast-rs" -version = "1.2.1" +name = "dlv-list" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] [[package]] name = "dpi" @@ -2907,9 +2941,9 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "dtoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" [[package]] name = "dtoa-short" @@ -2990,10 +3024,10 @@ dependencies = [ "s3s-policy", "serde", "serde_json", - "sha2 0.11.0-pre.4", + "sha2 0.11.0-pre.5", "siphasher 1.0.1", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -3013,9 +3047,9 @@ dependencies = [ [[package]] name = "either" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encoding_rs" @@ -3050,7 +3084,7 @@ checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3071,7 +3105,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3113,9 +3147,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener", "pin-project-lite", @@ -3164,9 +3198,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", "miniz_oxide", @@ -3178,6 +3212,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.5.0" @@ -3196,7 +3236,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3299,7 +3339,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3486,14 +3526,16 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -3578,7 +3620,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3720,7 +3762,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3735,7 +3777,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.7.1", + "indexmap 2.8.0", "slab", "tokio", "tokio-util", @@ -3744,9 +3786,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" dependencies = [ "cfg-if", "crunchy", @@ -3777,6 +3819,18 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.2", +] [[package]] name = "heck" @@ -3835,11 +3889,11 @@ dependencies = [ [[package]] name = "hmac" -version = "0.13.0-pre.4" +version = "0.13.0-pre.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b1fb14e4df79f9406b434b60acef9f45c26c50062cccf1346c6103b8c47d58" +checksum = "62c11fc82c6b89c906b4d26b7b5a305d0b3aebd4b458dd1bd0a7ed98c548a28e" dependencies = [ - "digest 0.11.0-pre.9", + "digest 0.11.0-pre.10", ] [[package]] @@ -3867,13 +3921,13 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", - "itoa 1.0.14", + "itoa 1.0.15", ] [[package]] @@ -3888,12 +3942,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -3901,9 +3955,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -3913,15 +3967,15 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] name = "hybrid-array" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9" +checksum = "4dab50e193aebe510fe0e40230145820e02f48dae0cf339ea4204e6e708ff7bd" dependencies = [ "typenum", ] @@ -3940,7 +3994,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.14", + "itoa 1.0.15", "pin-project-lite", "smallvec", "tokio", @@ -3957,7 +4011,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.23", + "rustls 0.23.25", "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", @@ -3980,9 +4034,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-channel", @@ -3990,6 +4044,7 @@ dependencies = [ "http", "http-body", "hyper", + "libc", "pin-project-lite", "socket2", "tokio", @@ -4020,7 +4075,7 @@ dependencies = [ "serde_json", "strum", "test-case", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -4028,16 +4083,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.0", ] [[package]] @@ -4090,9 +4146,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -4114,9 +4170,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -4135,9 +4191,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -4164,7 +4220,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -4206,9 +4262,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -4310,9 +4366,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "javascriptcore-rs" @@ -4361,10 +4417,11 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.2", "libc", ] @@ -4378,13 +4435,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "jsonwebtoken" version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "base64", + "base64 0.22.1", "js-sys", "pem", "ring", @@ -4539,9 +4607,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.170" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libdbus-sys" @@ -4607,12 +4675,30 @@ dependencies = [ "x11", ] +[[package]] +name = "libz-sys" +version = "1.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + [[package]] name = "litemap" version = "0.7.5" @@ -4663,9 +4749,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "longest-increasing-subsequence" @@ -4764,7 +4850,7 @@ dependencies = [ "manganis-core", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5144,10 +5230,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5234,6 +5320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb" dependencies = [ "bitflags 2.9.0", + "block2 0.6.0", "objc2 0.6.0", "objc2-core-foundation", "objc2-foundation 0.3.0", @@ -5297,7 +5384,6 @@ checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.9.0", "block2 0.5.1", - "dispatch", "libc", "objc2 0.5.2", ] @@ -5389,9 +5475,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "opaque-debug" @@ -5399,6 +5485,132 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "opentelemetry" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "768ee97dc5cd695a4dd4a69a0678fb42789666b5a89e8c0af48bb06c6e427120" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "opentelemetry-appender-tracing" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e716f864eb23007bdd9dc4aec381e188a1cee28eecf22066772b5fd822b9727d" +dependencies = [ + "opentelemetry", + "tracing", + "tracing-core", + "tracing-log", + "tracing-opentelemetry", + "tracing-subscriber", +] + +[[package]] +name = "opentelemetry-http" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46d7ab32b827b5b495bd90fa95a6cb65ccc293555dcc3199ae2937d2d237c8ed" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry", + "reqwest", + "tracing", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d899720fe06916ccba71c01d04ecd77312734e2de3467fd30d9d580c8ce85656" +dependencies = [ + "futures-core", + "http", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest", + "thiserror 2.0.12", + "tokio", + "tonic", + "tracing", +] + +[[package]] +name = "opentelemetry-prometheus" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ac8c4fc7bd450bcb5b1cbc7325755e86d9f82f1fd80ad8b3441887b715f6a2d" +dependencies = [ + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "prometheus", + "protobuf 2.28.0", + "tracing", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c40da242381435e18570d5b9d50aca2a4f4f4d8e146231adb4e7768023309b3" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b29a9f89f1a954936d5aa92f19b2feec3c8f3971d3e96206640db7f9706ae3" + +[[package]] +name = "opentelemetry-stdout" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e27d446dabd68610ef0b77d07b102ecde827a4596ea9c01a4d3811e945b286" +dependencies = [ + "chrono", + "futures-util", + "opentelemetry", + "opentelemetry_sdk", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afdefb21d1d47394abc1ba6c57363ab141be19e27cc70d0e422b7f303e4d290b" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "opentelemetry", + "percent-encoding", + "rand 0.9.0", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -5414,6 +5626,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -5510,16 +5732,16 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.9", + "redox_syscall 0.5.10", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "parquet" -version = "54.2.1" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88838dca3b84d41444a0341b19f347e8098a3898b0f21536654b8b799e11abd" +checksum = "bfb15796ac6f56b429fd99e33ba133783ad75b27c36b4b5ce06f1f82cc97754e" dependencies = [ "ahash 0.8.11", "arrow-array", @@ -5529,7 +5751,7 @@ dependencies = [ "arrow-ipc", "arrow-schema", "arrow-select", - "base64", + "base64 0.22.1", "brotli", "bytes", "chrono", @@ -5549,7 +5771,6 @@ dependencies = [ "tokio", "twox-hash", "zstd", - "zstd-sys", ] [[package]] @@ -5602,6 +5823,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -5618,7 +5845,7 @@ version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ - "base64", + "base64 0.22.1", "serde", ] @@ -5628,6 +5855,51 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +dependencies = [ + "memchr", + "thiserror 2.0.12", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "pest_meta" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +dependencies = [ + "once_cell", + "pest", + "sha2 0.10.8", +] + [[package]] name = "petgraph" version = "0.7.1" @@ -5635,7 +5907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.7.1", + "indexmap 2.8.0", ] [[package]] @@ -5770,22 +6042,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5813,9 +6085,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" @@ -5851,7 +6123,7 @@ dependencies = [ "serde_json", "strum", "test-case", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -5867,7 +6139,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix", + "rustix 0.38.44", "tracing", "windows-sys 0.59.0", ] @@ -5915,11 +6187,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy 0.8.24", ] [[package]] @@ -5930,12 +6202,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.29" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb" dependencies = [ "proc-macro2", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5959,9 +6231,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit 0.22.24", ] @@ -5998,9 +6270,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -6013,10 +6285,25 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "version_check", ] +[[package]] +name = "prometheus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot 0.12.3", + "protobuf 2.28.0", + "thiserror 1.0.69", +] + [[package]] name = "prost" version = "0.13.5" @@ -6043,7 +6330,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.98", + "syn 2.0.100", "tempfile", ] @@ -6057,7 +6344,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6071,9 +6358,15 @@ dependencies = [ [[package]] name = "protobuf" -version = "3.7.1" +version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3a7c64d9bf75b1b8d981124c14c179074e8caa7dfe7b6a12e6222ddcd0c8f72" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + +[[package]] +name = "protobuf" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" dependencies = [ "once_cell", "protobuf-support", @@ -6082,9 +6375,9 @@ dependencies = [ [[package]] name = "protobuf-support" -version = "3.7.1" +version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b088fd20b938a875ea00843b6faf48579462630015c3788d397ad6a786663252" +checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" dependencies = [ "thiserror 1.0.69", ] @@ -6097,7 +6390,7 @@ dependencies = [ "flatbuffers", "prost", "prost-build", - "protobuf", + "protobuf 3.7.2", "tokio", "tonic", "tonic-build", @@ -6132,9 +6425,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.37.2" +version = "0.37.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +checksum = "a4ce8c88de324ff838700f36fb6ab86c96df0e3c4ab6ef3a9b2044465cce1369" dependencies = [ "memchr", "serde", @@ -6142,37 +6435,39 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" dependencies = [ "bytes", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls 0.23.23", + "rustls 0.23.25", "socket2", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" dependencies = [ "bytes", - "getrandom 0.2.15", - "rand 0.8.5", + "getrandom 0.3.2", + "rand 0.9.0", "ring", "rustc-hash 2.1.1", - "rustls 0.23.23", + "rustls 0.23.25", "rustls-pki-types", "slab", - "thiserror 2.0.11", + "thiserror 2.0.12", "tinyvec", "tracing", "web-time", @@ -6180,9 +6475,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" +checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" dependencies = [ "cfg_aliases", "libc", @@ -6194,13 +6489,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.7.3" @@ -6234,7 +6535,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.21", + "zerocopy 0.8.24", ] [[package]] @@ -6291,7 +6592,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", ] [[package]] @@ -6324,6 +6625,36 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rdkafka" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b52c81ac3cac39c9639b95c20452076e74b8d9a71bc6fc4d83407af2ea6fff" +dependencies = [ + "futures-channel", + "futures-util", + "libc", + "log", + "rdkafka-sys", + "serde", + "serde_derive", + "serde_json", + "slab", + "tokio", +] + +[[package]] +name = "rdkafka-sys" +version = "4.8.0+2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced38182dc436b3d9df0c77976f37a67134df26b050df1f0006688e46fc4c8be" +dependencies = [ + "libc", + "libz-sys", + "num_enum", + "pkg-config", +] + [[package]] name = "recursive" version = "0.1.1" @@ -6341,7 +6672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" dependencies = [ "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6355,9 +6686,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ "bitflags 2.9.0", ] @@ -6381,7 +6712,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6445,11 +6776,11 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.12" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-channel", @@ -6471,7 +6802,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.23", + "rustls 0.23.25", "rustls-pemfile", "rustls-pki-types", "serde", @@ -6518,19 +6849,19 @@ dependencies = [ [[package]] name = "rfd" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a24763657bff09769a8ccf12c8b8a50416fb035fe199263b4c5071e4e3f006f" +checksum = "80c844748fdc82aae252ee4594a89b6e7ebef1063de7951545564cbc4e57075d" dependencies = [ - "ashpd 0.10.2", - "block2 0.5.1", - "core-foundation 0.10.0", - "core-foundation-sys", + "ashpd 0.11.0", + "block2 0.6.0", + "dispatch2", "js-sys", "log", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", + "objc2 0.6.0", + "objc2-app-kit 0.3.0", + "objc2-core-foundation", + "objc2-foundation 0.3.0", "pollster 0.4.0", "raw-window-handle 0.6.2", "urlencoding", @@ -6542,9 +6873,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.11" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", @@ -6576,6 +6907,18 @@ dependencies = [ "serde", ] +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.9.0", + "serde", + "serde_derive", +] + [[package]] name = "rust-embed" version = "8.6.0" @@ -6597,7 +6940,7 @@ dependencies = [ "quote", "rust-embed-utils", "shellexpand", - "syn 2.0.98", + "syn 2.0.100", "walkdir", ] @@ -6611,6 +6954,17 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rust-ini" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" +dependencies = [ + "cfg-if", + "ordered-multimap", + "trim-in-place", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -6659,7 +7013,6 @@ dependencies = [ "flatbuffers", "futures", "futures-util", - "h2", "http", "http-body", "hyper", @@ -6674,17 +7027,19 @@ dependencies = [ "mime", "mime_guess", "netif", + "once_cell", "pin-project-lite", "policy", "prost", "prost-build", "prost-types", - "protobuf", + "protobuf 3.7.2", "protos", "query", "rmp-serde", "rust-embed", - "rustls 0.23.23", + "rustfs-obs", + "rustls 0.23.25", "rustls-pemfile", "rustls-pki-types", "s3s", @@ -6703,6 +7058,7 @@ dependencies = [ "tower 0.5.2", "tower-http", "tracing", + "tracing-core", "tracing-error", "tracing-subscriber", "transform-stream", @@ -6720,7 +7076,7 @@ dependencies = [ "hex", "keyring", "lazy_static", - "rfd 0.15.2", + "rfd 0.15.3", "rust-embed", "serde", "serde_json", @@ -6730,6 +7086,35 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "rustfs-obs" +version = "0.0.1" +dependencies = [ + "async-trait", + "chrono", + "config", + "local-ip-address", + "opentelemetry", + "opentelemetry-appender-tracing", + "opentelemetry-otlp", + "opentelemetry-prometheus", + "opentelemetry-semantic-conventions", + "opentelemetry-stdout", + "opentelemetry_sdk", + "prometheus", + "rdkafka", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tracing", + "tracing-core", + "tracing-error", + "tracing-opentelemetry", + "tracing-subscriber", +] + [[package]] name = "rustix" version = "0.38.44" @@ -6739,7 +7124,20 @@ dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.3", "windows-sys 0.59.0", ] @@ -6757,16 +7155,16 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki 0.103.1", "subtle", "zeroize", ] @@ -6801,9 +7199,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "aws-lc-rs", "ring", @@ -6813,15 +7211,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "s3s" @@ -6837,15 +7235,15 @@ dependencies = [ "chrono", "crc32c", "crc32fast", - "digest 0.11.0-pre.9", + "digest 0.11.0-pre.10", "futures", "hex-simd", - "hmac 0.13.0-pre.4", + "hmac 0.13.0-pre.5", "http-body", "http-body-util", "httparse", "hyper", - "itoa 1.0.14", + "itoa 1.0.15", "memchr", "mime", "nom", @@ -6855,12 +7253,12 @@ dependencies = [ "quick-xml", "serde", "serde_urlencoded", - "sha1 0.11.0-pre.4", - "sha2 0.11.0-pre.4", + "sha1 0.11.0-pre.5", + "sha2 0.11.0-pre.5", "smallvec", "std-next", "sync_wrapper", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tower 0.5.2", @@ -6875,10 +7273,10 @@ name = "s3s-policy" version = "0.11.0-dev" source = "git+https://github.com/Nugine/s3s.git?rev=ab139f72fe768fb9d8cecfe36269451da1ca9779#ab139f72fe768fb9d8cecfe36269451da1ca9779" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6890,12 +7288,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -6970,9 +7362,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "send_wrapper" @@ -6991,9 +7383,9 @@ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -7011,22 +7403,22 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "serde_json" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "itoa 1.0.14", + "itoa 1.0.15", "memchr", "ryu", "serde", @@ -7034,11 +7426,11 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ - "itoa 1.0.14", + "itoa 1.0.15", "serde", ] @@ -7055,13 +7447,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7080,7 +7472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.14", + "itoa 1.0.15", "ryu", "serde", ] @@ -7124,7 +7516,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "xxhash-rust", ] @@ -7135,7 +7527,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f2aa8119b558a17992e0ac1fd07f080099564f24532858811ce04f742542440" dependencies = [ "server_fn_macro", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7161,13 +7553,13 @@ dependencies = [ [[package]] name = "sha1" -version = "0.11.0-pre.4" +version = "0.11.0-pre.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9540978cef7a8498211c1b1c14e5ce920fe5bd524ea84f4a3d72d4602515ae93" +checksum = "55f44e40722caefdd99383c25d3ae52a1094a1951215ae76f68837ece4e7f566" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.11.0-pre.9", + "digest 0.11.0-pre.10", ] [[package]] @@ -7183,13 +7575,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0-pre.4" +version = "0.11.0-pre.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540c0893cce56cdbcfebcec191ec8e0f470dd1889b6e7a0b503e310a94a168f5" +checksum = "19b4241d1a56954dce82cecda5c8e9c794eef6f53abe5e5216bac0a0ea71ffa7" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.11.0-pre.9", + "digest 0.11.0-pre.10", ] [[package]] @@ -7266,7 +7658,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", ] @@ -7308,7 +7700,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33a1b4f13e2bbf2f5b29d09dfebc9de69229ffee245aed80e3b70f9b5fd28c06" dependencies = [ "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7355,7 +7747,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7366,9 +7758,9 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -7425,7 +7817,7 @@ checksum = "da5fc6819faabb412da764b99d3b713bb55083c11e7e0c00144d386cd6a1939c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7436,9 +7828,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9156ebd5870ef293bfb43f91c7a74528d363ec0d424afe24160ed5a4343d08a" +checksum = "601f9201feb9b09c00266478bf459952b9ef9a6b94edb2f21eba14ab681a60a9" dependencies = [ "cc", "cfg-if", @@ -7455,19 +7847,19 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "std-next" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafdb55260d9b29c04fa52351e0db2a4aaeadc462cd884ccd7771c5a31aaf1aa" +checksum = "1258af3e6890fc6369f89e9d5c052c3406707aef3f8e836697155fb6e07153bf" dependencies = [ "simdutf8", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] name = "string_cache" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot 0.12.3", @@ -7513,7 +7905,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7535,9 +7927,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -7561,7 +7953,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7646,7 +8038,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7657,15 +8049,14 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.17.1" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.2", "once_cell", - "rustix", + "rustix 1.0.5", "windows-sys 0.59.0", ] @@ -7698,7 +8089,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7709,7 +8100,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "test-case-core", ] @@ -7730,11 +8121,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -7745,18 +8136,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7782,12 +8173,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", - "itoa 1.0.14", + "itoa 1.0.15", "libc", "num-conv", "num_threads", @@ -7799,15 +8190,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -7849,14 +8240,15 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" dependencies = [ "backtrace", "bytes", "libc", "mio", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2", @@ -7873,7 +8265,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7892,7 +8284,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.23", + "rustls 0.23.25", "tokio", ] @@ -7909,9 +8301,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", @@ -7948,7 +8340,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "toml_datetime", "winnow 0.5.40", ] @@ -7959,7 +8351,7 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "toml_datetime", "winnow 0.5.40", ] @@ -7970,11 +8362,11 @@ version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.7.3", + "winnow 0.7.4", ] [[package]] @@ -7986,7 +8378,7 @@ dependencies = [ "async-stream", "async-trait", "axum", - "base64", + "base64 0.22.1", "bytes", "flate2", "h2", @@ -8021,7 +8413,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -8131,7 +8523,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -8165,6 +8557,24 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd8e764bd6f5813fd8bebc3117875190c5b0415be8f7f8059bffb6ecd979c444" +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" @@ -8234,10 +8644,16 @@ dependencies = [ "objc2-foundation 0.3.0", "once_cell", "png", - "thiserror 2.0.11", + "thiserror 2.0.12", "windows-sys 0.59.0", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "try-lock" version = "0.2.5" @@ -8278,6 +8694,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "uds_windows" version = "1.1.0" @@ -8297,9 +8719,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" @@ -8379,11 +8801,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.15.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", "js-sys", "rand 0.9.0", "serde", @@ -8393,13 +8815,13 @@ dependencies = [ [[package]] name = "uuid-macro-internal" -version = "1.15.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9521621447c21497fac206ffe6e9f642f977c4f82eeba9201055f64884d9cb01" +checksum = "72dcd78c4f979627a754f5522cea6e6a25e55139056535fe6e69c506cd64a862" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -8408,6 +8830,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.0" @@ -8464,7 +8892,7 @@ checksum = "59195a1db0e95b920366d949ba5e0d3fc0e70b67c09be15ce5abb790106b0571" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -8481,9 +8909,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -8510,7 +8938,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -8545,7 +8973,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8572,66 +9000,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wayland-backend" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" -dependencies = [ - "cc", - "downcast-rs", - "rustix", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" -dependencies = [ - "bitflags 2.9.0", - "rustix", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" -dependencies = [ - "bitflags 2.9.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" -dependencies = [ - "proc-macro2", - "quick-xml", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" -dependencies = [ - "dlib", - "log", - "pkg-config", -] - [[package]] name = "web-sys" version = "0.3.77" @@ -8732,8 +9100,8 @@ dependencies = [ "webview2-com-sys", "windows", "windows-core 0.58.0", - "windows-implement", - "windows-interface", + "windows-implement 0.58.0", + "windows-interface 0.58.0", ] [[package]] @@ -8744,7 +9112,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -8767,7 +9135,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.44", ] [[package]] @@ -8813,24 +9181,28 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.52.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.58.0" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", - "windows-targets 0.52.6", + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.2", + "windows-strings 0.4.0", ] [[package]] @@ -8841,7 +9213,18 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -8852,24 +9235,35 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] name = "windows-link" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-registry" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ - "windows-result", - "windows-strings", - "windows-targets 0.52.6", + "windows-result 0.3.2", + "windows-strings 0.3.1", + "windows-targets 0.53.0", ] [[package]] @@ -8881,16 +9275,43 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -8966,7 +9387,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -8974,10 +9395,26 @@ dependencies = [ ] [[package]] -name = "windows-version" -version = "0.1.3" +name = "windows-targets" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bfbcc4996dd183ff1376a20ade1242da0d2dcaff83cc76710a588d24fd4c5db" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-version" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" dependencies = [ "windows-link", ] @@ -9000,6 +9437,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -9018,6 +9461,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -9036,12 +9485,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -9060,6 +9521,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -9078,6 +9545,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -9096,6 +9569,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -9114,6 +9593,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.5.40" @@ -9125,18 +9610,18 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.0", ] @@ -9168,7 +9653,7 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac0099a336829fbf54c26b5f620c68980ebbe37196772aeaf6118df4931b5cb0" dependencies = [ - "base64", + "base64 0.22.1", "block", "cocoa 0.26.0", "core-graphics 0.24.0", @@ -9248,6 +9733,17 @@ dependencies = [ "lzma-sys", ] +[[package]] +name = "yaml-rust2" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818913695e83ece1f8d2a1c52d54484b7b46d0f9c06beeb2649b9da50d9b512d" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + [[package]] name = "yoke" version = "0.7.5" @@ -9268,7 +9764,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure", ] @@ -9328,7 +9824,7 @@ dependencies = [ "tracing", "uds_windows", "windows-sys 0.59.0", - "winnow 0.7.3", + "winnow 0.7.4", "xdg-home", "zbus_macros 5.5.0", "zbus_names 4.2.0", @@ -9341,10 +9837,10 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "zvariant_utils 2.1.0", ] @@ -9354,10 +9850,10 @@ version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f325ad10eb0d0a3eb060203494c3b7ec3162a01a59db75d2deee100339709fc0" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "zbus_names 4.2.0", "zvariant 5.4.0", "zvariant_utils 3.2.0", @@ -9382,7 +9878,7 @@ checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" dependencies = [ "serde", "static_assertions", - "winnow 0.7.3", + "winnow 0.7.4", "zvariant 5.4.0", ] @@ -9392,17 +9888,16 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", "zerocopy-derive 0.7.35", ] [[package]] name = "zerocopy" -version = "0.8.21" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "zerocopy-derive 0.8.21", + "zerocopy-derive 0.8.24", ] [[package]] @@ -9413,18 +9908,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "zerocopy-derive" -version = "0.8.21" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -9444,7 +9939,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure", ] @@ -9473,7 +9968,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -9487,18 +9982,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", @@ -9529,7 +10024,7 @@ dependencies = [ "serde", "static_assertions", "url", - "winnow 0.7.3", + "winnow 0.7.4", "zvariant_derive 5.4.0", "zvariant_utils 3.2.0", ] @@ -9540,10 +10035,10 @@ version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "zvariant_utils 2.1.0", ] @@ -9553,10 +10048,10 @@ version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74170caa85b8b84cc4935f2d56a57c7a15ea6185ccdd7eadb57e6edd90f94b2f" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "zvariant_utils 3.2.0", ] @@ -9568,7 +10063,7 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -9581,6 +10076,6 @@ dependencies = [ "quote", "serde", "static_assertions", - "syn 2.0.98", - "winnow 0.7.3", + "syn 2.0.100", + "winnow 0.7.4", ] diff --git a/Cargo.toml b/Cargo.toml index 20f6a009a..0945e52b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "iam", # Identity and Access Management "crypto", # Cryptography and security features "cli/rustfs-gui", # Graphical user interface client + "packages/obs", # Observability utilities "s3select/api", "s3select/query", ] @@ -32,12 +33,13 @@ all = "warn" [workspace.dependencies] madmin = { path = "./madmin" } async-recursion = "1.0.5" -async-trait = "0.1.86" +async-trait = "0.1.87" backon = "1.3.0" bytes = "1.9.0" bytesize = "1.3.0" -chrono = { version = "0.4.39", features = ["serde"] } +chrono = { version = "0.4.40", features = ["serde"] } clap = { version = "4.5.31", features = ["derive", "env"] } +config = "0.15.9" datafusion = "46.0.0" derive_builder = "0.20.2" dioxus = { version = "0.6.3", features = ["router"] } @@ -50,7 +52,6 @@ common = { path = "./common/common" } policy = { path = "./policy" } hex = "0.4.3" hyper = "1.6.0" -#hyper-rustls = { version = "0.27.5", features = ["http2"] } hyper-util = { version = "0.1.10", features = [ "tokio", "server-auto", @@ -65,7 +66,16 @@ lazy_static = "1.5.0" local-ip-address = "0.6.3" mime = "0.3.17" netif = "0.1.6" +once_cell = "1.21.1" +opentelemetry = { version = "0.29" } +opentelemetry-appender-tracing = { version = "0.29.1", features = ["experimental_use_tracing_span_context", "experimental_metadata_attributes"] } +opentelemetry_sdk = { version = "0.29" } +opentelemetry-stdout = { version = "0.29.0" } +opentelemetry-otlp = { version = "0.29" } +opentelemetry-prometheus = { version = "0.29" } +opentelemetry-semantic-conventions = { version = "0.29.0", features = ["semconv_experimental"] } pin-project-lite = "0.2" +prometheus = "0.13.4" # pin-utils = "0.1.0" prost = "0.13.4" prost-build = "0.13.4" @@ -73,10 +83,12 @@ prost-types = "0.13.4" protobuf = "3.7" protos = { path = "./common/protos" } rand = "0.8.5" -reqwest = { version = "0.12.12", default-features = false, features = ["rustls-tls", "charset", "http2", "macos-system-configuration", "stream", "blocking"] } +rdkafka = { version = "0.37", features = ["tokio"] } +reqwest = { version = "0.12.15", default-features = false, features = ["rustls-tls", "charset", "http2", "macos-system-configuration", "stream", "json", "blocking"] } rfd = { version = "0.15.2", default-features = false, features = ["xdg-portal", "tokio"] } rmp = "0.8.14" rmp-serde = "1.3.0" +rustfs-obs = { path = "packages/obs", version = "0.0.1" } rust-embed = "8.6.0" rustls = { version = "0.23" } rustls-pki-types = "1.11.0" @@ -90,15 +102,15 @@ serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.138" sha2 = "0.10.8" tempfile = "3.16.0" -thiserror = "2.0.11" -time = { version = "0.3.37", features = [ +thiserror = "2.0.12" +time = { version = "0.3.41", features = [ "std", "parsing", "formatting", "macros", "serde", ] } -tokio = { version = "1.43.0", features = ["fs", "rt-multi-thread"] } +tokio = { version = "1.44.0", features = ["fs", "rt-multi-thread"] } tonic = { version = "0.12.3", features = ["gzip"] } tonic-build = "0.12.3" tonic-reflection = "0.12" @@ -108,9 +120,11 @@ tokio-util = { version = "0.7.13", features = ["io", "compat"] } tower = { version = "0.5.2", features = ["timeout"] } tower-http = { version = "0.6.2", features = ["cors"] } tracing = "0.1.41" +tracing-core = "0.1.33" tracing-error = "0.2.1" tracing-subscriber = { version = "0.3.19", features = ["env-filter", "time"] } tracing-appender = "0.2.3" +tracing-opentelemetry = "0.30" transform-stream = "0.3.1" url = "2.5.4" uuid = { version = "1.15.1", features = [ @@ -125,7 +139,6 @@ workers = { path = "./common/workers" } test-case = "3.3.1" snafu = "0.8.5" - [profile.wasm-dev] inherits = "dev" opt-level = 1 diff --git a/README.md b/README.md index dbb7e9f6e..217ab3c14 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ export RUSTFS_VOLUMES="./target/volume/test" export RUSTFS_ADDRESS="0.0.0.0:9000" export RUSTFS_CONSOLE_ENABLE=true export RUSTFS_CONSOLE_ADDRESS="0.0.0.0:9001" +export RUSTFS_OBS_CONFIG="config/obs.toml" ``` You need replace your real data folder: @@ -47,3 +48,47 @@ You need replace your real data folder: ``` ./rustfs /data/rustfs ``` + +## How to deploy the observability stack + +The OpenTelemetry Collector offers a vendor-agnostic implementation on how to receive, process, and export telemetry +data. It removes the need to run, operate, and maintain multiple agents/collectors in order to support open-source +observability data formats (e.g. Jaeger, Prometheus, etc.) sending to one or more open-source or commercial back-ends. + +1. Enter the `.docker/observability` directory, +2. Run the following command: + +```bash +docker-compose -f docker-compose.yml up -d +``` + +3. Access the Grafana dashboard by navigating to `http://localhost:3000` in your browser. The default username and + password are `admin` and `admin`, respectively. + +4. Access the Jaeger dashboard by navigating to `http://localhost:16686` in your browser. + +5. Access the Prometheus dashboard by navigating to `http://localhost:9090` in your browser. + +## Create a new Observability configuration file + +#### 1. Enter the `config` directory, + +#### 2. Copy `obs.toml.example` to `obs.toml` + +#### 3. Modify the `obs.toml` configuration file + +##### 3.1. Modify the `endpoint` value to the address of the OpenTelemetry Collector + +##### 3.2. Modify the `service_name` value to the name of the service + +##### 3.3. Modify the `service_version` value to the version of the service + +##### 3.4. Modify the `deployment_environment` value to the environment of the service + +##### 3.5. Modify the `meter_interval` value to export interval + +##### 3.6. Modify the `sample_ratio` value to the sample ratio + +##### 3.7. Modify the `use_stdout` value to export to stdout + + diff --git a/config/obs.example.toml b/config/obs.example.toml new file mode 100644 index 000000000..0ba434ada --- /dev/null +++ b/config/obs.example.toml @@ -0,0 +1,33 @@ +[observability] +endpoint = "http://localhost:4317" # Default is "http://localhost:4317" if not specified +use_stdout = false # Output with stdout, true output, false no output +sample_ratio = 2.0 +meter_interval = 30 +service_name = "rustfs" +service_version = "0.1.0" +environment = "develop" +looger_level = "info" + +[sinks] +[sinks.kafka] # Kafka sink is disabled by default +enabled = false +bootstrap_servers = "localhost:9092" +topic = "logs" +batch_size = 100 # Default is 100 if not specified +batch_timeout_ms = 1000 # Default is 1000ms if not specified + +[sinks.webhook] +enabled = false +endpoint = "http://localhost:8080/webhook" +auth_token = "" +batch_size = 100 # Default is 3 if not specified +batch_timeout_ms = 1000 # Default is 100ms if not specified + +[sinks.file] +enabled = true +path = "logs/app.log" +batch_size = 100 +batch_timeout_ms = 1000 # Default is 8192 bytes if not specified + +[logger] +queue_capacity = 10000 \ No newline at end of file diff --git a/ecstore/src/endpoints.rs b/ecstore/src/endpoints.rs index 51a9c0b82..ea44edf42 100644 --- a/ecstore/src/endpoints.rs +++ b/ecstore/src/endpoints.rs @@ -1,4 +1,4 @@ -use tracing::warn; +use tracing::{instrument, warn}; use crate::{ disk::endpoint::{Endpoint, EndpointType}, @@ -407,7 +407,7 @@ pub struct PoolEndpoints { pub platform: String, } -/// list of list of endpoints +/// list of endpoints #[derive(Debug, Clone, Default)] pub struct EndpointServerPools(pub Vec); @@ -532,6 +532,8 @@ impl EndpointServerPools { nodes } + + #[instrument] pub fn hosts_sorted(&self) -> Vec> { let (mut peers, local) = self.peers(); @@ -604,7 +606,6 @@ impl EndpointServerPools { #[cfg(test)] mod test { - use super::*; use std::path::Path; diff --git a/ecstore/src/global.rs b/ecstore/src/global.rs index 8735249c9..f6fc4bdb8 100644 --- a/ecstore/src/global.rs +++ b/ecstore/src/global.rs @@ -61,7 +61,7 @@ pub fn get_global_deployment_id() -> Option { pub fn set_global_endpoints(eps: Vec) { GLOBAL_Endpoints .set(EndpointServerPools::from(eps)) - .expect("GLOBAL_Endpoints set faild") + .expect("GLOBAL_Endpoints set failed") } pub fn get_global_endpoints() -> EndpointServerPools { diff --git a/ecstore/src/heal/data_scanner.rs b/ecstore/src/heal/data_scanner.rs index 52ded12a2..6701f7ba8 100644 --- a/ecstore/src/heal/data_scanner.rs +++ b/ecstore/src/heal/data_scanner.rs @@ -352,7 +352,7 @@ impl CurrentScannerCycle { let str_len = rmp::decode::read_str_len(&mut cur)?; - // !!! Vec::with_capacity(str_len) 失败,vec!正常 + // !!!Vec::with_capacity(str_len) 失败,vec! 正常 let mut field_buff = vec![0u8; str_len as usize]; cur.read_exact(&mut field_buff)?; @@ -435,7 +435,7 @@ impl ScannerItem { pub async fn apply_versions_actions(&self, fivs: &[FileInfo]) -> Result> { let obj_infos = self.apply_newer_noncurrent_version_limit(fivs).await?; - if obj_infos.len() >= SCANNER_EXCESS_OBJECT_VERSIONS.load(Ordering::SeqCst).try_into().unwrap() { + if obj_infos.len() >= >::try_into(SCANNER_EXCESS_OBJECT_VERSIONS.load(Ordering::SeqCst)).unwrap() { // todo } @@ -445,10 +445,7 @@ impl ScannerItem { } if cumulative_size - >= SCANNER_EXCESS_OBJECT_VERSIONS_TOTAL_SIZE - .load(Ordering::SeqCst) - .try_into() - .unwrap() + >= >::try_into(SCANNER_EXCESS_OBJECT_VERSIONS_TOTAL_SIZE.load(Ordering::SeqCst)).unwrap() { //todo } @@ -684,16 +681,13 @@ impl FolderScanner { } let should_compact = self.new_cache.info.name != folder.name - && existing_folders.len() + new_folders.len() >= DATA_SCANNER_COMPACT_AT_FOLDERS.try_into().unwrap() - || existing_folders.len() + new_folders.len() >= DATA_SCANNER_FORCE_COMPACT_AT_FOLDERS.try_into().unwrap(); + && existing_folders.len() + new_folders.len() + >= >::try_into(DATA_SCANNER_COMPACT_AT_FOLDERS).unwrap() + || existing_folders.len() + new_folders.len() + >= >::try_into(DATA_SCANNER_FORCE_COMPACT_AT_FOLDERS).unwrap(); let total_folders = existing_folders.len() + new_folders.len(); - if total_folders - > SCANNER_EXCESS_FOLDERS - .load(std::sync::atomic::Ordering::SeqCst) - .try_into() - .unwrap() - { + if total_folders > >::try_into(SCANNER_EXCESS_FOLDERS.load(Ordering::SeqCst)).unwrap() { let _prefix_name = format!("{}/", folder.name.trim_end_matches('/')); // todo: notification } @@ -957,7 +951,7 @@ impl FolderScanner { if !into.compacted && self.new_cache.info.name != folder.name { let mut flat = self.new_cache.size_recursive(&this_hash.key()).unwrap_or_default(); flat.compacted = true; - let compact = if flat.objects < DATA_SCANNER_COMPACT_LEAST_OBJECT.try_into().unwrap() { + let compact = if flat.objects < >::try_into(DATA_SCANNER_COMPACT_LEAST_OBJECT).unwrap() { true } else { // Compact if we only have objects as children... diff --git a/ecstore/src/heal/data_usage_cache.rs b/ecstore/src/heal/data_usage_cache.rs index 7bc33df81..9111d2a7f 100644 --- a/ecstore/src/heal/data_usage_cache.rs +++ b/ecstore/src/heal/data_usage_cache.rs @@ -588,7 +588,7 @@ impl DataUsageCache { Some(e) => e, None => return, }; - if top_e.children.len() > DATA_SCANNER_FORCE_COMPACT_AT_FOLDERS.try_into().unwrap() { + if top_e.children.len() > >::try_into(DATA_SCANNER_FORCE_COMPACT_AT_FOLDERS).unwrap() { self.reduce_children_of(&hash_path(&self.info.name), limit, true); } if self.cache.len() <= limit { diff --git a/iam/src/lib.rs b/iam/src/lib.rs index d88da7555..5d3ca626c 100644 --- a/iam/src/lib.rs +++ b/iam/src/lib.rs @@ -6,7 +6,7 @@ use policy::auth::Credentials; use std::sync::{Arc, OnceLock}; use store::object::ObjectStore; use sys::IamSys; -use tracing::debug; +use tracing::{debug, instrument}; pub mod cache; pub mod error; @@ -51,6 +51,7 @@ pub fn get_global_action_cred() -> Option { GLOBAL_ACTIVE_CRED.get().cloned() } +#[instrument] pub async fn init_iam_sys(ecstore: Arc) -> Result<()> { debug!("init iam system"); let s = IamCache::new(ObjectStore::new(ecstore)).await; diff --git a/packages/obs/Cargo.toml b/packages/obs/Cargo.toml new file mode 100644 index 000000000..4290b76b3 --- /dev/null +++ b/packages/obs/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "rustfs-obs" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[lints] +workspace = true + +[features] +default = ["file"] +kafka = ["dep:rdkafka"] +webhook = ["dep:reqwest"] +file = [] + +[dependencies] +async-trait = { workspace = true } +chrono = { workspace = true } +config = { workspace = true } +opentelemetry = { workspace = true } +opentelemetry-appender-tracing = { workspace = true, features = ["experimental_use_tracing_span_context", "experimental_metadata_attributes"] } +opentelemetry_sdk = { workspace = true, features = ["rt-tokio"] } +opentelemetry-stdout = { workspace = true } +opentelemetry-otlp = { workspace = true, features = ["grpc-tonic", "gzip-tonic"] } +opentelemetry-prometheus = { workspace = true } +opentelemetry-semantic-conventions = { workspace = true, features = ["semconv_experimental"] } +prometheus = { workspace = true } +serde = { workspace = true } +tracing = { workspace = true, features = ["std", "attributes"] } +tracing-core = { workspace = true } +tracing-error = { workspace = true } +tracing-opentelemetry = { workspace = true } +tracing-subscriber = { workspace = true, features = ["registry", "std", "fmt", "env-filter", "tracing-log", "time", "local-time", "json"] } +tokio = { workspace = true, features = ["sync", "fs", "rt-multi-thread"] } +rdkafka = { workspace = true, features = ["tokio"], optional = true } +reqwest = { workspace = true, optional = true, default-features = false } +serde_json = { workspace = true } +thiserror = { workspace = true } +local-ip-address = { workspace = true } + + +[dev-dependencies] +chrono = { workspace = true } +opentelemetry = { workspace = true } +opentelemetry_sdk = { workspace = true, features = ["rt-tokio"] } +opentelemetry-stdout = { workspace = true } +opentelemetry-otlp = { workspace = true, features = ["grpc-tonic"] } +opentelemetry-semantic-conventions = { workspace = true, features = ["semconv_experimental"] } +tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true, features = ["std", "attributes"] } +tracing-subscriber = { workspace = true, features = ["registry", "std", "fmt"] } \ No newline at end of file diff --git a/packages/obs/examples/config.toml b/packages/obs/examples/config.toml new file mode 100644 index 000000000..135dd39ac --- /dev/null +++ b/packages/obs/examples/config.toml @@ -0,0 +1,31 @@ +[observability] +endpoint = "http://localhost:4317" +use_stdout = true +sample_ratio = 1 +meter_interval = 30 +service_name = "rustfs_obs" +service_version = "0.1.0" +deployment_environment = "develop" + +[sinks] +[sinks.kafka] +enabled = false +bootstrap_servers = "localhost:9092" +topic = "logs" +batch_size = 100 # Default is 100 if not specified +batch_timeout_ms = 1000 # Default is 1000ms if not specified + +[sinks.webhook] +enabled = false +url = "http://localhost:8080/webhook" +batch_size = 100 # Default is 3 if not specified +batch_timeout_ms = 1000 # Default is 100ms if not specified + +[sinks.file] +enabled = true +path = "logs/app.log" +batch_size = 100 +batch_timeout_ms = 1000 # Default is 8192 bytes if not specified + +[logger] +queue_capacity = 10000 \ No newline at end of file diff --git a/packages/obs/examples/server.rs b/packages/obs/examples/server.rs new file mode 100644 index 000000000..0af2fcd02 --- /dev/null +++ b/packages/obs/examples/server.rs @@ -0,0 +1,88 @@ +use opentelemetry::global; +use rustfs_obs::{get_logger, init_obs, load_config, log_info, BaseLogEntry, ServerLogEntry}; +use std::collections::HashMap; +use std::time::{Duration, SystemTime}; +use tracing::{info, instrument}; +use tracing_core::Level; + +#[tokio::main] +async fn main() { + let obs_conf = Some("packages/obs/examples/config.toml".to_string()); + let config = load_config(obs_conf); + let (_logger, _guard) = init_obs(config.clone()).await; + let span = tracing::span!(Level::INFO, "main"); + let _enter = span.enter(); + info!("Program starts"); + // Simulate the operation + tokio::time::sleep(Duration::from_millis(100)).await; + run( + "service-demo".to_string(), + "object-demo".to_string(), + "user-demo".to_string(), + "service-demo".to_string(), + ) + .await; + info!("Program ends"); +} + +#[instrument(fields(bucket, object, user))] +async fn run(bucket: String, object: String, user: String, service_name: String) { + let start_time = SystemTime::now(); + info!("Log module initialization is completed service_name: {:?}", service_name); + + // Record Metrics + let meter = global::meter("rustfs"); + let request_duration = meter.f64_histogram("s3_request_duration_seconds").build(); + request_duration.record( + start_time.elapsed().unwrap().as_secs_f64(), + &[opentelemetry::KeyValue::new("operation", "run")], + ); + + let base_entry = BaseLogEntry::new() + .message(Some("run logger api_handler info".to_string())) + .request_id(Some("request_id".to_string())) + .timestamp(chrono::DateTime::from(start_time)) + .tags(Some(HashMap::default())); + + let server_entry = ServerLogEntry::new(Level::INFO, "api_handler".to_string()) + .with_base(base_entry) + .user_id(Some(user.clone())) + .add_field("operation".to_string(), "login".to_string()) + .add_field("bucket".to_string(), bucket.clone()) + .add_field("object".to_string(), object.clone()); + + let result = get_logger().lock().await.log_server_entry(server_entry).await; + info!("Logging is completed {:?}", result); + put_object("bucket".to_string(), "object".to_string(), "user".to_string()).await; + info!("Logging is completed"); + tokio::time::sleep(Duration::from_secs(2)).await; + info!("Program run ends"); +} + +#[instrument(fields(bucket, object, user))] +async fn put_object(bucket: String, object: String, user: String) { + let start_time = SystemTime::now(); + info!("Starting put_object operation time: {:?}", start_time); + + let meter = global::meter("rustfs"); + let request_duration = meter.f64_histogram("s3_request_duration_seconds").build(); + request_duration.record( + start_time.elapsed().unwrap().as_secs_f64(), + &[opentelemetry::KeyValue::new("operation", "put_object")], + ); + + info!( + "Starting PUT operation content: bucket = {}, object = {}, user = {},start_time = {}", + bucket, + object, + user, + start_time.elapsed().unwrap().as_secs_f64() + ); + + let result = log_info("put_object logger info", "put_object").await; + info!("put_object is completed {:?}", result); + // Simulate the operation + tokio::time::sleep(Duration::from_millis(100)).await; + + info!("PUT operation completed"); +} diff --git a/packages/obs/src/config.rs b/packages/obs/src/config.rs new file mode 100644 index 000000000..be20c5b0b --- /dev/null +++ b/packages/obs/src/config.rs @@ -0,0 +1,182 @@ +use crate::global::{ENVIRONMENT, LOGGER_LEVEL, METER_INTERVAL, SAMPLE_RATIO, SERVICE_NAME, SERVICE_VERSION}; +use config::{Config, File, FileFormat}; +use serde::Deserialize; +use std::env; + +/// OpenTelemetry Configuration +/// Add service name, service version, environment +/// Add interval time for metric collection +/// Add sample ratio for trace sampling +/// Add endpoint for metric collection +/// Add use_stdout for output to stdout +/// Add logger level for log level +#[derive(Debug, Deserialize, Clone)] +pub struct OtelConfig { + pub endpoint: String, + pub use_stdout: Option, + pub sample_ratio: Option, + pub meter_interval: Option, + pub service_name: Option, + pub service_version: Option, + pub environment: Option, + pub logger_level: Option, +} + +impl Default for OtelConfig { + fn default() -> Self { + OtelConfig { + endpoint: "".to_string(), + use_stdout: Some(true), + sample_ratio: Some(SAMPLE_RATIO), + meter_interval: Some(METER_INTERVAL), + service_name: Some(SERVICE_NAME.to_string()), + service_version: Some(SERVICE_VERSION.to_string()), + environment: Some(ENVIRONMENT.to_string()), + logger_level: Some(LOGGER_LEVEL.to_string()), + } + } +} + +/// Kafka Sink Configuration - Add batch parameters +#[derive(Debug, Deserialize, Clone, Default)] +pub struct KafkaSinkConfig { + pub enabled: bool, + pub bootstrap_servers: String, + pub topic: String, + pub batch_size: Option, // Batch size, default 100 + pub batch_timeout_ms: Option, // Batch timeout time, default 1000ms +} + +/// Webhook Sink Configuration - Add Retry Parameters +#[derive(Debug, Deserialize, Clone, Default)] +pub struct WebhookSinkConfig { + pub enabled: bool, + pub endpoint: String, + pub auth_token: String, + pub max_retries: Option, // Maximum number of retry times, default 3 + pub retry_delay_ms: Option, // Retry the delay cardinality, default 100ms +} + +/// File Sink Configuration - Add buffering parameters +#[derive(Debug, Deserialize, Clone)] +pub struct FileSinkConfig { + pub enabled: bool, + pub path: String, + pub buffer_size: Option, // Write buffer size, default 8192 + pub flush_interval_ms: Option, // Refresh interval time, default 1000ms + pub flush_threshold: Option, // Refresh threshold, default 100 logs +} + +impl Default for FileSinkConfig { + fn default() -> Self { + FileSinkConfig { + enabled: true, + path: "logs/app.log".to_string(), + buffer_size: Some(8192), + flush_interval_ms: Some(1000), + flush_threshold: Some(100), + } + } +} + +/// Sink configuration collection +#[derive(Debug, Deserialize, Clone, Default)] +pub struct SinkConfig { + pub kafka: KafkaSinkConfig, + pub webhook: WebhookSinkConfig, + pub file: FileSinkConfig, +} + +///Logger Configuration +#[derive(Debug, Deserialize, Clone)] +pub struct LoggerConfig { + pub queue_capacity: Option, +} + +impl Default for LoggerConfig { + fn default() -> Self { + LoggerConfig { + queue_capacity: Some(1000), + } + } +} + +/// Overall application configuration +/// Add observability, sinks, and logger configuration +/// +/// Observability: OpenTelemetry configuration +/// Sinks: Kafka, Webhook, File sink configuration +/// Logger: Logger configuration +/// +/// # Example +/// ``` +/// use rustfs_obs::AppConfig; +/// use rustfs_obs::load_config; +/// +/// let config = load_config(None); +/// ``` +#[derive(Debug, Deserialize, Clone, Default)] +pub struct AppConfig { + pub observability: OtelConfig, + pub sinks: SinkConfig, + pub logger: LoggerConfig, +} + +const DEFAULT_CONFIG_FILE: &str = "obs"; + +/// Loading the configuration file +/// Supports TOML, YAML and .env formats, read in order by priority +/// +/// # Parameters +/// - `config_dir`: Configuration file path +/// +/// # Returns +/// Configuration information +/// +/// # Example +/// ``` +/// use rustfs_obs::AppConfig; +/// use rustfs_obs::load_config; +/// +/// let config = load_config(None); +/// ``` +pub fn load_config(config_dir: Option) -> AppConfig { + let config_dir = if let Some(path) = config_dir { + // If a path is provided, check if it's empty + if path.is_empty() { + // If empty, use the default config file name + DEFAULT_CONFIG_FILE.to_string() + } else { + // Use the provided path + let path = std::path::Path::new(&path); + if path.extension().is_some() { + // If path has extension, use it as is (extension will be added by Config::builder) + path.with_extension("").to_string_lossy().into_owned() + } else { + // If path is a directory, append the default config file name + path.to_string_lossy().into_owned() + } + } + } else { + // If no path provided, use current directory + default config file + match env::current_dir() { + Ok(dir) => dir.join(DEFAULT_CONFIG_FILE).to_string_lossy().into_owned(), + Err(_) => { + eprintln!("Warning: Failed to get current directory, using default config file"); + DEFAULT_CONFIG_FILE.to_string() + } + } + }; + + // Log using proper logging instead of println when possible + println!("Using config file base: {}", config_dir); + + let config = Config::builder() + .add_source(File::with_name(config_dir.as_str()).format(FileFormat::Toml).required(false)) + .add_source(File::with_name(config_dir.as_str()).format(FileFormat::Yaml).required(false)) + .add_source(config::Environment::with_prefix("")) + .build() + .unwrap_or_default(); + + config.try_deserialize().unwrap_or_default() +} diff --git a/packages/obs/src/entry/args.rs b/packages/obs/src/entry/args.rs new file mode 100644 index 000000000..1099398ac --- /dev/null +++ b/packages/obs/src/entry/args.rs @@ -0,0 +1,74 @@ +use crate::entry::ObjectVersion; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Args - defines the arguments for API operations +/// Args is used to define the arguments for API operations. +/// +/// # Example +/// ``` +/// use rustfs_obs::Args; +/// use std::collections::HashMap; +/// +/// let args = Args::new() +/// .set_bucket(Some("my-bucket".to_string())) +/// .set_object(Some("my-object".to_string())) +/// .set_version_id(Some("123".to_string())) +/// .set_metadata(Some(HashMap::new())); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)] +pub struct Args { + #[serde(rename = "bucket", skip_serializing_if = "Option::is_none")] + pub bucket: Option, + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "versionId", skip_serializing_if = "Option::is_none")] + pub version_id: Option, + #[serde(rename = "objects", skip_serializing_if = "Option::is_none")] + pub objects: Option>, + #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")] + pub metadata: Option>, +} + +impl Args { + /// Create a new Args object + pub fn new() -> Self { + Args { + bucket: None, + object: None, + version_id: None, + objects: None, + metadata: None, + } + } + + /// Set the bucket + pub fn set_bucket(mut self, bucket: Option) -> Self { + self.bucket = bucket; + self + } + + /// Set the object + pub fn set_object(mut self, object: Option) -> Self { + self.object = object; + self + } + + /// Set the version ID + pub fn set_version_id(mut self, version_id: Option) -> Self { + self.version_id = version_id; + self + } + + /// Set the objects + pub fn set_objects(mut self, objects: Option>) -> Self { + self.objects = objects; + self + } + + /// Set the metadata + pub fn set_metadata(mut self, metadata: Option>) -> Self { + self.metadata = metadata; + self + } +} diff --git a/packages/obs/src/entry/audit.rs b/packages/obs/src/entry/audit.rs new file mode 100644 index 000000000..0aba2bac3 --- /dev/null +++ b/packages/obs/src/entry/audit.rs @@ -0,0 +1,453 @@ +use crate::{BaseLogEntry, LogRecord, ObjectVersion}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; + +/// API details structure +/// ApiDetails is used to define the details of an API operation +/// +/// The `ApiDetails` structure contains the following fields: +/// - `name` - the name of the API operation +/// - `bucket` - the bucket name +/// - `object` - the object name +/// - `objects` - the list of objects +/// - `status` - the status of the API operation +/// - `status_code` - the status code of the API operation +/// - `input_bytes` - the input bytes +/// - `output_bytes` - the output bytes +/// - `header_bytes` - the header bytes +/// - `time_to_first_byte` - the time to first byte +/// - `time_to_first_byte_in_ns` - the time to first byte in nanoseconds +/// - `time_to_response` - the time to response +/// - `time_to_response_in_ns` - the time to response in nanoseconds +/// +/// The `ApiDetails` structure contains the following methods: +/// - `new` - create a new `ApiDetails` with default values +/// - `set_name` - set the name +/// - `set_bucket` - set the bucket +/// - `set_object` - set the object +/// - `set_objects` - set the objects +/// - `set_status` - set the status +/// - `set_status_code` - set the status code +/// - `set_input_bytes` - set the input bytes +/// - `set_output_bytes` - set the output bytes +/// - `set_header_bytes` - set the header bytes +/// - `set_time_to_first_byte` - set the time to first byte +/// - `set_time_to_first_byte_in_ns` - set the time to first byte in nanoseconds +/// - `set_time_to_response` - set the time to response +/// - `set_time_to_response_in_ns` - set the time to response in nanoseconds +/// +/// # Example +/// ``` +/// use rustfs_obs::ApiDetails; +/// use rustfs_obs::ObjectVersion; +/// +/// let api = ApiDetails::new() +/// .set_name(Some("GET".to_string())) +/// .set_bucket(Some("my-bucket".to_string())) +/// .set_object(Some("my-object".to_string())) +/// .set_objects(vec![ObjectVersion::new_with_object_name("my-object".to_string())]) +/// .set_status(Some("OK".to_string())) +/// .set_status_code(Some(200)) +/// .set_input_bytes(100) +/// .set_output_bytes(200) +/// .set_header_bytes(Some(50)) +/// .set_time_to_first_byte(Some("100ms".to_string())) +/// .set_time_to_first_byte_in_ns(Some("100000000ns".to_string())) +/// .set_time_to_response(Some("200ms".to_string())) +/// .set_time_to_response_in_ns(Some("200000000ns".to_string())); +/// ``` +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)] +pub struct ApiDetails { + #[serde(rename = "name", skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(rename = "bucket", skip_serializing_if = "Option::is_none")] + pub bucket: Option, + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "objects", skip_serializing_if = "Vec::is_empty", default)] + pub objects: Vec, + #[serde(rename = "status", skip_serializing_if = "Option::is_none")] + pub status: Option, + #[serde(rename = "statusCode", skip_serializing_if = "Option::is_none")] + pub status_code: Option, + #[serde(rename = "rx")] + pub input_bytes: i64, + #[serde(rename = "tx")] + pub output_bytes: i64, + #[serde(rename = "txHeaders", skip_serializing_if = "Option::is_none")] + pub header_bytes: Option, + #[serde(rename = "timeToFirstByte", skip_serializing_if = "Option::is_none")] + pub time_to_first_byte: Option, + #[serde(rename = "timeToFirstByteInNS", skip_serializing_if = "Option::is_none")] + pub time_to_first_byte_in_ns: Option, + #[serde(rename = "timeToResponse", skip_serializing_if = "Option::is_none")] + pub time_to_response: Option, + #[serde(rename = "timeToResponseInNS", skip_serializing_if = "Option::is_none")] + pub time_to_response_in_ns: Option, +} + +impl ApiDetails { + /// Create a new `ApiDetails` with default values + pub fn new() -> Self { + ApiDetails { + name: None, + bucket: None, + object: None, + objects: Vec::new(), + status: None, + status_code: None, + input_bytes: 0, + output_bytes: 0, + header_bytes: None, + time_to_first_byte: None, + time_to_first_byte_in_ns: None, + time_to_response: None, + time_to_response_in_ns: None, + } + } + + /// Set the name + pub fn set_name(mut self, name: Option) -> Self { + self.name = name; + self + } + + /// Set the bucket + pub fn set_bucket(mut self, bucket: Option) -> Self { + self.bucket = bucket; + self + } + + /// Set the object + pub fn set_object(mut self, object: Option) -> Self { + self.object = object; + self + } + + /// Set the objects + pub fn set_objects(mut self, objects: Vec) -> Self { + self.objects = objects; + self + } + + /// Set the status + pub fn set_status(mut self, status: Option) -> Self { + self.status = status; + self + } + + /// Set the status code + pub fn set_status_code(mut self, status_code: Option) -> Self { + self.status_code = status_code; + self + } + + /// Set the input bytes + pub fn set_input_bytes(mut self, input_bytes: i64) -> Self { + self.input_bytes = input_bytes; + self + } + + /// Set the output bytes + pub fn set_output_bytes(mut self, output_bytes: i64) -> Self { + self.output_bytes = output_bytes; + self + } + + /// Set the header bytes + pub fn set_header_bytes(mut self, header_bytes: Option) -> Self { + self.header_bytes = header_bytes; + self + } + + /// Set the time to first byte + pub fn set_time_to_first_byte(mut self, time_to_first_byte: Option) -> Self { + self.time_to_first_byte = time_to_first_byte; + self + } + + /// Set the time to first byte in nanoseconds + pub fn set_time_to_first_byte_in_ns(mut self, time_to_first_byte_in_ns: Option) -> Self { + self.time_to_first_byte_in_ns = time_to_first_byte_in_ns; + self + } + + /// Set the time to response + pub fn set_time_to_response(mut self, time_to_response: Option) -> Self { + self.time_to_response = time_to_response; + self + } + + /// Set the time to response in nanoseconds + pub fn set_time_to_response_in_ns(mut self, time_to_response_in_ns: Option) -> Self { + self.time_to_response_in_ns = time_to_response_in_ns; + self + } +} + +/// Entry - audit entry logs +/// AuditLogEntry is used to define the structure of an audit log entry +/// +/// The `AuditLogEntry` structure contains the following fields: +/// - `base` - the base log entry +/// - `version` - the version of the audit log entry +/// - `deployment_id` - the deployment ID +/// - `event` - the event +/// - `entry_type` - the type of audit message +/// - `api` - the API details +/// - `remote_host` - the remote host +/// - `user_agent` - the user agent +/// - `req_path` - the request path +/// - `req_host` - the request host +/// - `req_claims` - the request claims +/// - `req_query` - the request query +/// - `req_header` - the request header +/// - `resp_header` - the response header +/// - `access_key` - the access key +/// - `parent_user` - the parent user +/// - `error` - the error +/// +/// The `AuditLogEntry` structure contains the following methods: +/// - `new` - create a new `AuditEntry` with default values +/// - `new_with_values` - create a new `AuditEntry` with version, time, event and api details +/// - `with_base` - set the base log entry +/// - `set_version` - set the version +/// - `set_deployment_id` - set the deployment ID +/// - `set_event` - set the event +/// - `set_entry_type` - set the entry type +/// - `set_api` - set the API details +/// - `set_remote_host` - set the remote host +/// - `set_user_agent` - set the user agent +/// - `set_req_path` - set the request path +/// - `set_req_host` - set the request host +/// - `set_req_claims` - set the request claims +/// - `set_req_query` - set the request query +/// - `set_req_header` - set the request header +/// - `set_resp_header` - set the response header +/// - `set_access_key` - set the access key +/// - `set_parent_user` - set the parent user +/// - `set_error` - set the error +/// +/// # Example +/// ``` +/// use rustfs_obs::AuditLogEntry; +/// use rustfs_obs::ApiDetails; +/// use std::collections::HashMap; +/// +/// let entry = AuditLogEntry::new() +/// .set_version("1.0".to_string()) +/// .set_deployment_id(Some("123".to_string())) +/// .set_event("event".to_string()) +/// .set_entry_type(Some("type".to_string())) +/// .set_api(ApiDetails::new()) +/// .set_remote_host(Some("remote-host".to_string())) +/// .set_user_agent(Some("user-agent".to_string())) +/// .set_req_path(Some("req-path".to_string())) +/// .set_req_host(Some("req-host".to_string())) +/// .set_req_claims(Some(HashMap::new())) +/// .set_req_query(Some(HashMap::new())) +/// .set_req_header(Some(HashMap::new())) +/// .set_resp_header(Some(HashMap::new())) +/// .set_access_key(Some("access-key".to_string())) +/// .set_parent_user(Some("parent-user".to_string())) +/// .set_error(Some("error".to_string())); +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +pub struct AuditLogEntry { + #[serde(flatten)] + pub base: BaseLogEntry, + pub version: String, + #[serde(rename = "deploymentid", skip_serializing_if = "Option::is_none")] + pub deployment_id: Option, + pub event: String, + // Class of audit message - S3, admin ops, bucket management + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub entry_type: Option, + pub api: ApiDetails, + #[serde(rename = "remotehost", skip_serializing_if = "Option::is_none")] + pub remote_host: Option, + #[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")] + pub user_agent: Option, + #[serde(rename = "requestPath", skip_serializing_if = "Option::is_none")] + pub req_path: Option, + #[serde(rename = "requestHost", skip_serializing_if = "Option::is_none")] + pub req_host: Option, + #[serde(rename = "requestClaims", skip_serializing_if = "Option::is_none")] + pub req_claims: Option>, + #[serde(rename = "requestQuery", skip_serializing_if = "Option::is_none")] + pub req_query: Option>, + #[serde(rename = "requestHeader", skip_serializing_if = "Option::is_none")] + pub req_header: Option>, + #[serde(rename = "responseHeader", skip_serializing_if = "Option::is_none")] + pub resp_header: Option>, + #[serde(rename = "accessKey", skip_serializing_if = "Option::is_none")] + pub access_key: Option, + #[serde(rename = "parentUser", skip_serializing_if = "Option::is_none")] + pub parent_user: Option, + #[serde(rename = "error", skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +impl AuditLogEntry { + /// Create a new `AuditEntry` with default values + pub fn new() -> Self { + AuditLogEntry { + base: BaseLogEntry::new(), + version: String::new(), + deployment_id: None, + event: String::new(), + entry_type: None, + api: ApiDetails::new(), + remote_host: None, + user_agent: None, + req_path: None, + req_host: None, + req_claims: None, + req_query: None, + req_header: None, + resp_header: None, + access_key: None, + parent_user: None, + error: None, + } + } + + /// Create a new `AuditEntry` with version, time, event and api details + pub fn new_with_values(version: String, time: DateTime, event: String, api: ApiDetails) -> Self { + let mut base = BaseLogEntry::new(); + base.timestamp = time; + + AuditLogEntry { + base, + version, + deployment_id: None, + event, + entry_type: None, + api, + remote_host: None, + user_agent: None, + req_path: None, + req_host: None, + req_claims: None, + req_query: None, + req_header: None, + resp_header: None, + access_key: None, + parent_user: None, + error: None, + } + } + + /// Set the base log entry + pub fn with_base(mut self, base: BaseLogEntry) -> Self { + self.base = base; + self + } + + /// Set the version + pub fn set_version(mut self, version: String) -> Self { + self.version = version; + self + } + + /// Set the deployment ID + pub fn set_deployment_id(mut self, deployment_id: Option) -> Self { + self.deployment_id = deployment_id; + self + } + + /// Set the event + pub fn set_event(mut self, event: String) -> Self { + self.event = event; + self + } + + /// Set the entry type + pub fn set_entry_type(mut self, entry_type: Option) -> Self { + self.entry_type = entry_type; + self + } + + /// Set the API details + pub fn set_api(mut self, api: ApiDetails) -> Self { + self.api = api; + self + } + + /// Set the remote host + pub fn set_remote_host(mut self, remote_host: Option) -> Self { + self.remote_host = remote_host; + self + } + + /// Set the user agent + pub fn set_user_agent(mut self, user_agent: Option) -> Self { + self.user_agent = user_agent; + self + } + + /// Set the request path + pub fn set_req_path(mut self, req_path: Option) -> Self { + self.req_path = req_path; + self + } + + /// Set the request host + pub fn set_req_host(mut self, req_host: Option) -> Self { + self.req_host = req_host; + self + } + + /// Set the request claims + pub fn set_req_claims(mut self, req_claims: Option>) -> Self { + self.req_claims = req_claims; + self + } + + /// Set the request query + pub fn set_req_query(mut self, req_query: Option>) -> Self { + self.req_query = req_query; + self + } + + /// Set the request header + pub fn set_req_header(mut self, req_header: Option>) -> Self { + self.req_header = req_header; + self + } + + /// Set the response header + pub fn set_resp_header(mut self, resp_header: Option>) -> Self { + self.resp_header = resp_header; + self + } + + /// Set the access key + pub fn set_access_key(mut self, access_key: Option) -> Self { + self.access_key = access_key; + self + } + + /// Set the parent user + pub fn set_parent_user(mut self, parent_user: Option) -> Self { + self.parent_user = parent_user; + self + } + + /// Set the error + pub fn set_error(mut self, error: Option) -> Self { + self.error = error; + self + } +} + +impl LogRecord for AuditLogEntry { + fn to_json(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| String::from("{}")) + } + + fn get_timestamp(&self) -> DateTime { + self.base.timestamp + } +} diff --git a/packages/obs/src/entry/base.rs b/packages/obs/src/entry/base.rs new file mode 100644 index 000000000..422646e09 --- /dev/null +++ b/packages/obs/src/entry/base.rs @@ -0,0 +1,92 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; + +/// Base log entry structure shared by all log types +/// This structure is used to serialize log entries to JSON +/// and send them to the log sinks +/// This structure is also used to deserialize log entries from JSON +/// This structure is also used to store log entries in the database +/// This structure is also used to query log entries from the database +/// +/// The `BaseLogEntry` structure contains the following fields: +/// - `timestamp` - the timestamp of the log entry +/// - `request_id` - the request ID of the log entry +/// - `message` - the message of the log entry +/// - `tags` - the tags of the log entry +/// +/// The `BaseLogEntry` structure contains the following methods: +/// - `new` - create a new `BaseLogEntry` with default values +/// - `message` - set the message +/// - `request_id` - set the request ID +/// - `tags` - set the tags +/// - `timestamp` - set the timestamp +/// +/// # Example +/// ``` +/// use rustfs_obs::BaseLogEntry; +/// use chrono::{DateTime, Utc}; +/// use std::collections::HashMap; +/// +/// let timestamp = Utc::now(); +/// let request = Some("req-123".to_string()); +/// let message = Some("This is a log message".to_string()); +/// let tags = Some(HashMap::new()); +/// +/// let entry = BaseLogEntry::new() +/// .timestamp(timestamp) +/// .request_id(request) +/// .message(message) +/// .tags(tags); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Default)] +pub struct BaseLogEntry { + #[serde(rename = "time")] + pub timestamp: DateTime, + + #[serde(rename = "requestID", skip_serializing_if = "Option::is_none")] + pub request_id: Option, + + #[serde(rename = "message", skip_serializing_if = "Option::is_none")] + pub message: Option, + + #[serde(rename = "tags", skip_serializing_if = "Option::is_none")] + pub tags: Option>, +} + +impl BaseLogEntry { + /// Create a new BaseLogEntry with default values + pub fn new() -> Self { + BaseLogEntry { + timestamp: Utc::now(), + request_id: None, + message: None, + tags: None, + } + } + + /// Set the message + pub fn message(mut self, message: Option) -> Self { + self.message = message; + self + } + + /// Set the request ID + pub fn request_id(mut self, request_id: Option) -> Self { + self.request_id = request_id; + self + } + + /// Set the tags + pub fn tags(mut self, tags: Option>) -> Self { + self.tags = tags; + self + } + + /// Set the timestamp + pub fn timestamp(mut self, timestamp: DateTime) -> Self { + self.timestamp = timestamp; + self + } +} diff --git a/packages/obs/src/entry/mod.rs b/packages/obs/src/entry/mod.rs new file mode 100644 index 000000000..72dae2611 --- /dev/null +++ b/packages/obs/src/entry/mod.rs @@ -0,0 +1,143 @@ +pub(crate) mod args; +pub(crate) mod audit; +pub(crate) mod base; +pub(crate) mod unified; + +use serde::de::Error; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use tracing_core::Level; + +/// ObjectVersion is used across multiple modules +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct ObjectVersion { + #[serde(rename = "name")] + pub object_name: String, + #[serde(rename = "versionId", skip_serializing_if = "Option::is_none")] + pub version_id: Option, +} + +impl ObjectVersion { + /// Create a new ObjectVersion object + pub fn new() -> Self { + ObjectVersion { + object_name: String::new(), + version_id: None, + } + } + + /// Create a new ObjectVersion with object name + pub fn new_with_object_name(object_name: String) -> Self { + ObjectVersion { + object_name, + version_id: None, + } + } + + /// Set the object name + pub fn set_object_name(mut self, object_name: String) -> Self { + self.object_name = object_name; + self + } + + /// Set the version ID + pub fn set_version_id(mut self, version_id: Option) -> Self { + self.version_id = version_id; + self + } +} + +/// Log kind/level enum +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum LogKind { + #[serde(rename = "INFO")] + Info, + #[serde(rename = "WARNING")] + Warning, + #[serde(rename = "ERROR")] + Error, + #[serde(rename = "FATAL")] + Fatal, +} + +impl Default for LogKind { + fn default() -> Self { + LogKind::Info + } +} + +/// Trait for types that can be serialized to JSON and have a timestamp +/// This trait is used by `ServerLogEntry` to convert the log entry to JSON +/// and get the timestamp of the log entry +/// This trait is implemented by `ServerLogEntry` +/// +/// # Example +/// ``` +/// use rustfs_obs::LogRecord; +/// use chrono::{DateTime, Utc}; +/// use rustfs_obs::ServerLogEntry; +/// use tracing_core::Level; +/// +/// let log_entry = ServerLogEntry::new(Level::INFO, "api_handler".to_string()); +/// let json = log_entry.to_json(); +/// let timestamp = log_entry.get_timestamp(); +/// ``` +pub trait LogRecord { + fn to_json(&self) -> String; + fn get_timestamp(&self) -> chrono::DateTime; +} + +/// Wrapper for `tracing_core::Level` to implement `Serialize` and `Deserialize` +/// for `ServerLogEntry` +/// This is necessary because `tracing_core::Level` does not implement `Serialize` +/// and `Deserialize` +/// This is a workaround to allow `ServerLogEntry` to be serialized and deserialized +/// using `serde` +/// +/// # Example +/// ``` +/// use rustfs_obs::SerializableLevel; +/// use tracing_core::Level; +/// +/// let level = Level::INFO; +/// let serializable_level = SerializableLevel::from(level); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SerializableLevel(pub Level); + +impl From for SerializableLevel { + fn from(level: Level) -> Self { + SerializableLevel(level) + } +} + +impl From for Level { + fn from(serializable_level: SerializableLevel) -> Self { + serializable_level.0 + } +} + +impl Serialize for SerializableLevel { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.0.as_str()) + } +} + +impl<'de> Deserialize<'de> for SerializableLevel { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match s.as_str() { + "TRACE" => Ok(SerializableLevel(Level::TRACE)), + "DEBUG" => Ok(SerializableLevel(Level::DEBUG)), + "INFO" => Ok(SerializableLevel(Level::INFO)), + "WARN" => Ok(SerializableLevel(Level::WARN)), + "ERROR" => Ok(SerializableLevel(Level::ERROR)), + _ => Err(D::Error::custom("unknown log level")), + } + } +} diff --git a/packages/obs/src/entry/unified.rs b/packages/obs/src/entry/unified.rs new file mode 100644 index 000000000..598ebe792 --- /dev/null +++ b/packages/obs/src/entry/unified.rs @@ -0,0 +1,279 @@ +use crate::{AuditLogEntry, BaseLogEntry, LogKind, LogRecord, SerializableLevel}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use tracing_core::Level; + +/// Server log entry with structured fields +/// ServerLogEntry is used to log structured log entries from the server +/// +/// The `ServerLogEntry` structure contains the following fields: +/// - `base` - the base log entry +/// - `level` - the log level +/// - `source` - the source of the log entry +/// - `user_id` - the user ID +/// - `fields` - the structured fields of the log entry +/// +/// The `ServerLogEntry` structure contains the following methods: +/// - `new` - create a new `ServerLogEntry` with specified level and source +/// - `with_base` - set the base log entry +/// - `user_id` - set the user ID +/// - `fields` - set the fields +/// - `add_field` - add a field +/// +/// # Example +/// ``` +/// use rustfs_obs::ServerLogEntry; +/// use tracing_core::Level; +/// +/// let entry = ServerLogEntry::new(Level::INFO, "test_module".to_string()) +/// .user_id(Some("user-456".to_string())) +/// .add_field("operation".to_string(), "login".to_string()); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ServerLogEntry { + #[serde(flatten)] + pub base: BaseLogEntry, + + pub level: SerializableLevel, + pub source: String, + + #[serde(rename = "userId", skip_serializing_if = "Option::is_none")] + pub user_id: Option, + + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub fields: Vec<(String, String)>, +} + +impl ServerLogEntry { + /// Create a new ServerLogEntry with specified level and source + pub fn new(level: Level, source: String) -> Self { + ServerLogEntry { + base: BaseLogEntry::new(), + level: SerializableLevel(level), + source, + user_id: None, + fields: Vec::new(), + } + } + + /// Set the base log entry + pub fn with_base(mut self, base: BaseLogEntry) -> Self { + self.base = base; + self + } + + /// Set the user ID + pub fn user_id(mut self, user_id: Option) -> Self { + self.user_id = user_id; + self + } + + /// Set fields + pub fn fields(mut self, fields: Vec<(String, String)>) -> Self { + self.fields = fields; + self + } + + /// Add a field + pub fn add_field(mut self, key: String, value: String) -> Self { + self.fields.push((key, value)); + self + } +} + +impl LogRecord for ServerLogEntry { + fn to_json(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| String::from("{}")) + } + + fn get_timestamp(&self) -> DateTime { + self.base.timestamp + } +} + +/// Console log entry structure +/// ConsoleLogEntry is used to log console log entries +/// The `ConsoleLogEntry` structure contains the following fields: +/// - `base` - the base log entry +/// - `level` - the log level +/// - `console_msg` - the console message +/// - `node_name` - the node name +/// - `err` - the error message +/// The `ConsoleLogEntry` structure contains the following methods: +/// - `new` - create a new `ConsoleLogEntry` +/// - `new_with_console_msg` - create a new `ConsoleLogEntry` with console message and node name +/// - `with_base` - set the base log entry +/// - `set_level` - set the log level +/// - `set_node_name` - set the node name +/// - `set_console_msg` - set the console message +/// - `set_err` - set the error message +/// # Example +/// ``` +/// use rustfs_obs::ConsoleLogEntry; +/// +/// let entry = ConsoleLogEntry::new_with_console_msg("Test message".to_string(), "node-123".to_string()); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ConsoleLogEntry { + #[serde(flatten)] + pub base: BaseLogEntry, + + pub level: LogKind, + pub console_msg: String, + pub node_name: String, + + #[serde(skip)] + pub err: Option, +} + +impl ConsoleLogEntry { + /// Create a new ConsoleLogEntry + pub fn new() -> Self { + ConsoleLogEntry { + base: BaseLogEntry::new(), + level: LogKind::Info, + console_msg: String::new(), + node_name: String::new(), + err: None, + } + } + + /// Create a new ConsoleLogEntry with console message and node name + pub fn new_with_console_msg(console_msg: String, node_name: String) -> Self { + ConsoleLogEntry { + base: BaseLogEntry::new(), + level: LogKind::Info, + console_msg, + node_name, + err: None, + } + } + + /// Set the base log entry + pub fn with_base(mut self, base: BaseLogEntry) -> Self { + self.base = base; + self + } + + /// Set the log level + pub fn set_level(mut self, level: LogKind) -> Self { + self.level = level; + self + } + + /// Set the node name + pub fn set_node_name(mut self, node_name: String) -> Self { + self.node_name = node_name; + self + } + + /// Set the console message + pub fn set_console_msg(mut self, console_msg: String) -> Self { + self.console_msg = console_msg; + self + } + + /// Set the error message + pub fn set_err(mut self, err: Option) -> Self { + self.err = err; + self + } +} + +impl LogRecord for ConsoleLogEntry { + fn to_json(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| String::from("{}")) + } + + fn get_timestamp(&self) -> DateTime { + self.base.timestamp + } +} + +/// Unified log entry type +/// UnifiedLogEntry is used to log different types of log entries +/// +/// The `UnifiedLogEntry` enum contains the following variants: +/// - `Server` - a server log entry +/// - `Audit` - an audit log entry +/// - `Console` - a console log entry +/// +/// The `UnifiedLogEntry` enum contains the following methods: +/// - `to_json` - convert the log entry to JSON +/// - `get_timestamp` - get the timestamp of the log entry +/// +/// # Example +/// ``` +/// use rustfs_obs::{UnifiedLogEntry, ServerLogEntry}; +/// use tracing_core::Level; +/// +/// let server_entry = ServerLogEntry::new(Level::INFO, "test_module".to_string()); +/// let unified = UnifiedLogEntry::Server(server_entry); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum UnifiedLogEntry { + #[serde(rename = "server")] + Server(ServerLogEntry), + + #[serde(rename = "audit")] + Audit(AuditLogEntry), + + #[serde(rename = "console")] + Console(ConsoleLogEntry), +} + +impl LogRecord for UnifiedLogEntry { + fn to_json(&self) -> String { + match self { + UnifiedLogEntry::Server(entry) => entry.to_json(), + UnifiedLogEntry::Audit(entry) => entry.to_json(), + UnifiedLogEntry::Console(entry) => entry.to_json(), + } + } + + fn get_timestamp(&self) -> DateTime { + match self { + UnifiedLogEntry::Server(entry) => entry.get_timestamp(), + UnifiedLogEntry::Audit(entry) => entry.get_timestamp(), + UnifiedLogEntry::Console(entry) => entry.get_timestamp(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_base_log_entry() { + let base = BaseLogEntry::new() + .request_id(Some("req-123".to_string())) + .message(Some("Test message".to_string())); + + assert_eq!(base.request_id, Some("req-123".to_string())); + assert_eq!(base.message, Some("Test message".to_string())); + } + + #[test] + fn test_server_log_entry() { + let entry = ServerLogEntry::new(Level::INFO, "test_module".to_string()) + .user_id(Some("user-456".to_string())) + .add_field("operation".to_string(), "login".to_string()); + + assert_eq!(entry.level.0, Level::INFO); + assert_eq!(entry.source, "test_module"); + assert_eq!(entry.user_id, Some("user-456".to_string())); + assert_eq!(entry.fields.len(), 1); + assert_eq!(entry.fields[0], ("operation".to_string(), "login".to_string())); + } + + #[test] + fn test_unified_log_entry_json() { + let server_entry = ServerLogEntry::new(Level::INFO, "test_source".to_string()); + let unified = UnifiedLogEntry::Server(server_entry); + + let json = unified.to_json(); + assert!(json.contains("test_source")); + } +} diff --git a/packages/obs/src/global.rs b/packages/obs/src/global.rs new file mode 100644 index 000000000..41c5e555e --- /dev/null +++ b/packages/obs/src/global.rs @@ -0,0 +1,89 @@ +use crate::telemetry::OtelGuard; +use std::sync::{Arc, Mutex}; +use tokio::sync::{OnceCell, SetError}; +use tracing::{error, info}; + +pub(crate) const USE_STDOUT: bool = true; +pub(crate) const SERVICE_NAME: &str = "RustFS"; +pub(crate) const SAMPLE_RATIO: f64 = 1.0; +pub(crate) const METER_INTERVAL: u64 = 60; +pub(crate) const SERVICE_VERSION: &str = "0.1.0"; +pub(crate) const ENVIRONMENT: &str = "production"; +pub(crate) const LOGGER_LEVEL: &str = "info"; + +/// Global guard for OpenTelemetry tracing +static GLOBAL_GUARD: OnceCell>> = OnceCell::const_new(); + +/// Error type for global guard operations +#[derive(Debug, thiserror::Error)] +pub enum GuardError { + #[error("Failed to set global guard: {0}")] + SetError(#[from] SetError>>), + #[error("Global guard not initialized")] + NotInitialized, +} + +/// Set the global guard for OpenTelemetry +/// +/// # Arguments +/// * `guard` - The OtelGuard instance to set globally +/// +/// # Returns +/// * `Ok(())` if successful +/// * `Err(GuardError)` if setting fails +/// +/// # Example +/// ```rust +/// use rustfs_obs::{init_telemetry, load_config, set_global_guard}; +/// +/// async fn init() -> Result<(), Box> { +/// let config = load_config(None); +/// let guard = init_telemetry(&config.observability).await?; +/// set_global_guard(guard)?; +/// Ok(()) +/// } +/// ``` +pub fn set_global_guard(guard: OtelGuard) -> Result<(), GuardError> { + info!("Initializing global OpenTelemetry guard"); + GLOBAL_GUARD.set(Arc::new(Mutex::new(guard))).map_err(GuardError::SetError) +} + +/// Get the global guard for OpenTelemetry +/// +/// # Returns +/// * `Ok(Arc>)` if guard exists +/// * `Err(GuardError)` if guard not initialized +/// +/// # Example +/// ```rust +/// use rustfs_obs::get_global_guard; +/// +/// async fn trace_operation() -> Result<(), Box> { +/// let guard = get_global_guard()?; +/// let _lock = guard.lock().unwrap(); +/// // Perform traced operation +/// Ok(()) +/// } +/// ``` +pub fn get_global_guard() -> Result>, GuardError> { + GLOBAL_GUARD.get().cloned().ok_or(GuardError::NotInitialized) +} + +/// Try to get the global guard for OpenTelemetry +/// +/// # Returns +/// * `Some(Arc>)` if guard exists +/// * `None` if guard not initialized +pub fn try_get_global_guard() -> Option>> { + GLOBAL_GUARD.get().cloned() +} + +#[cfg(test)] +mod tests { + use super::*; + #[tokio::test] + async fn test_get_uninitialized_guard() { + let result = get_global_guard(); + assert!(matches!(result, Err(GuardError::NotInitialized))); + } +} diff --git a/packages/obs/src/lib.rs b/packages/obs/src/lib.rs new file mode 100644 index 000000000..843c52367 --- /dev/null +++ b/packages/obs/src/lib.rs @@ -0,0 +1,94 @@ +/// # obs +/// +/// `obs` is a logging and observability library for Rust. +/// It provides a simple and easy-to-use interface for logging and observability. +/// It is built on top of the `log` crate and `opentelemetry` crate. +/// +/// ## Features +/// - Structured logging +/// - Distributed tracing +/// - Metrics collection +/// - Log processing worker +/// - Multiple sinks +/// - Configuration-based setup +/// - Telemetry guard +/// - Global logger +/// - Log levels +/// - Log entry types +/// - Log record +/// - Object version +/// - Local IP address +/// +/// ## Usage +/// +/// ```rust +/// use rustfs_obs::{AppConfig, init_obs}; +/// +/// let config = AppConfig::default(); +/// let (logger, guard) = init_obs(config); +/// ``` +mod config; +mod entry; +mod global; +mod logger; +mod sink; +mod telemetry; +mod utils; +mod worker; +pub use config::load_config; +pub use config::{AppConfig, OtelConfig}; +pub use entry::args::Args; +pub use entry::audit::{ApiDetails, AuditLogEntry}; +pub use entry::base::BaseLogEntry; +pub use entry::unified::{ConsoleLogEntry, ServerLogEntry, UnifiedLogEntry}; +pub use entry::{LogKind, LogRecord, ObjectVersion, SerializableLevel}; +pub use global::{get_global_guard, set_global_guard, try_get_global_guard, GuardError}; +pub use logger::{ensure_logger_initialized, log_debug, log_error, log_info, log_trace, log_warn, log_with_context}; +pub use logger::{get_global_logger, init_global_logger, locked_logger, start_logger}; +pub use logger::{log_init_state, InitLogStatus}; +pub use logger::{LogError, Logger}; +pub use sink::Sink; +use std::sync::Arc; +pub use telemetry::init_telemetry; +pub use telemetry::{get_global_registry, metrics}; +use tokio::sync::Mutex; +pub use utils::{get_local_ip, get_local_ip_with_default}; +pub use worker::start_worker; + +/// Initialize the observability module +/// +/// # Parameters +/// - `config`: Configuration information +/// +/// # Returns +/// A tuple containing the logger and the telemetry guard +/// +/// # Example +/// ``` +/// use rustfs_obs::{AppConfig, init_obs}; +/// +/// let config = AppConfig::default(); +/// let (logger, guard) = init_obs(config); +/// ``` +pub async fn init_obs(config: AppConfig) -> (Arc>, telemetry::OtelGuard) { + let guard = init_telemetry(&config.observability); + let sinks = sink::create_sinks(&config).await; + let logger = init_global_logger(&config, sinks).await; + (logger, guard) +} + +/// Get the global logger instance +/// This function returns a reference to the global logger instance. +/// +/// # Returns +/// A reference to the global logger instance +/// +/// # Example +/// ``` +/// use rustfs_obs::get_logger; +/// +/// let logger = get_logger(); +/// ``` +pub fn get_logger() -> &'static Arc> { + get_global_logger() +} diff --git a/packages/obs/src/logger.rs b/packages/obs/src/logger.rs new file mode 100644 index 000000000..77862a942 --- /dev/null +++ b/packages/obs/src/logger.rs @@ -0,0 +1,534 @@ +use crate::global::{ENVIRONMENT, SERVICE_NAME, SERVICE_VERSION}; +use crate::{AppConfig, AuditLogEntry, BaseLogEntry, ConsoleLogEntry, OtelConfig, ServerLogEntry, Sink, UnifiedLogEntry}; +use std::sync::Arc; +use std::time::SystemTime; +use tokio::sync::mpsc::{self, Receiver, Sender}; +use tokio::sync::{Mutex, OnceCell}; +use tracing_core::Level; + +// Add the global instance at the module level +static GLOBAL_LOGGER: OnceCell>> = OnceCell::const_new(); + +/// Server log processor +#[derive(Debug)] +pub struct Logger { + sender: Sender, // Log sending channel + queue_capacity: usize, +} + +impl Logger { + /// Create a new Logger instance + /// Returns Logger and corresponding Receiver + pub fn new(config: &AppConfig) -> (Self, Receiver) { + // Get queue capacity from configuration, or use default values 10000 + let queue_capacity = config.logger.queue_capacity.unwrap_or(10000); + let (sender, receiver) = mpsc::channel(queue_capacity); + (Logger { sender, queue_capacity }, receiver) + } + + /// get the queue capacity + /// This function returns the queue capacity. + /// # Returns + /// The queue capacity + /// # Example + /// ``` + /// use rustfs_obs::Logger; + /// async fn example(logger: &Logger) { + /// let _ = logger.get_queue_capacity(); + /// } + /// ``` + pub fn get_queue_capacity(&self) -> usize { + self.queue_capacity + } + + /// Log a server entry + #[tracing::instrument(skip(self), fields(log_source = "logger_server"))] + pub async fn log_server_entry(&self, entry: ServerLogEntry) -> Result<(), LogError> { + self.log_entry(UnifiedLogEntry::Server(entry)).await + } + + /// Log an audit entry + #[tracing::instrument(skip(self), fields(log_source = "logger_audit"))] + pub async fn log_audit_entry(&self, entry: AuditLogEntry) -> Result<(), LogError> { + self.log_entry(UnifiedLogEntry::Audit(entry)).await + } + + /// Log a console entry + #[tracing::instrument(skip(self), fields(log_source = "logger_console"))] + pub async fn log_console_entry(&self, entry: ConsoleLogEntry) -> Result<(), LogError> { + self.log_entry(UnifiedLogEntry::Console(entry)).await + } + + /// Asynchronous logging of unified log entries + #[tracing::instrument(skip(self), fields(log_source = "logger"))] + pub async fn log_entry(&self, entry: UnifiedLogEntry) -> Result<(), LogError> { + // Extract information for tracing based on entry type + match &entry { + UnifiedLogEntry::Server(server) => { + tracing::Span::current() + .record("log_level", &server.level.0.as_str()) + .record("log_message", &server.base.message.as_deref().unwrap_or("")) + .record("source", &server.source); + + // Generate tracing event based on log level + match server.level.0 { + Level::ERROR => { + tracing::error!(target: "server_logs", message = %server.base.message.as_deref().unwrap_or("")); + } + Level::WARN => { + tracing::warn!(target: "server_logs", message = %server.base.message.as_deref().unwrap_or("")); + } + Level::INFO => { + tracing::info!(target: "server_logs", message = %server.base.message.as_deref().unwrap_or("")); + } + Level::DEBUG => { + tracing::debug!(target: "server_logs", message = %server.base.message.as_deref().unwrap_or("")); + } + Level::TRACE => { + tracing::trace!(target: "server_logs", message = %server.base.message.as_deref().unwrap_or("")); + } + } + } + UnifiedLogEntry::Audit(audit) => { + tracing::info!( + target: "audit_logs", + event = %audit.event, + api = %audit.api.name.as_deref().unwrap_or("unknown"), + message = %audit.base.message.as_deref().unwrap_or("") + ); + } + UnifiedLogEntry::Console(console) => { + let level_str = match console.level { + crate::LogKind::Info => "INFO", + crate::LogKind::Warning => "WARN", + crate::LogKind::Error => "ERROR", + crate::LogKind::Fatal => "FATAL", + }; + + tracing::info!( + target: "console_logs", + level = %level_str, + node = %console.node_name, + message = %console.console_msg + ); + } + } + + // Send logs to async queue with improved error handling + match self.sender.try_send(entry) { + Ok(_) => Ok(()), + Err(mpsc::error::TrySendError::Full(entry)) => { + // Processing strategy when queue is full + tracing::warn!("Log queue full, applying backpressure"); + match tokio::time::timeout(std::time::Duration::from_millis(500), self.sender.send(entry)).await { + Ok(Ok(_)) => Ok(()), + Ok(Err(_)) => Err(LogError::SendFailed("Channel closed")), + Err(_) => Err(LogError::Timeout("Queue backpressure timeout")), + } + } + Err(mpsc::error::TrySendError::Closed(_)) => Err(LogError::SendFailed("Logger channel closed")), + } + } + + /// Write log with context information + /// This function writes log messages with context information. + /// + /// # Parameters + /// - `message`: Message to be logged + /// - `source`: Source of the log + /// - `request_id`: Request ID + /// - `user_id`: User ID + /// - `fields`: Additional fields + /// + /// # Returns + /// Result indicating whether the operation was successful + /// + /// # Example + /// ``` + /// use tracing_core::Level; + /// use rustfs_obs::Logger; + /// + /// async fn example(logger: &Logger) { + /// let _ = logger.write_with_context("This is an information message", "example",Level::INFO, Some("req-12345".to_string()), Some("user-6789".to_string()), vec![("endpoint".to_string(), "/api/v1/data".to_string())]).await; + /// } + pub async fn write_with_context( + &self, + message: &str, + source: &str, + level: Level, + request_id: Option, + user_id: Option, + fields: Vec<(String, String)>, + ) -> Result<(), LogError> { + let base = BaseLogEntry::new().message(Some(message.to_string())).request_id(request_id); + + let server_entry = ServerLogEntry::new(level, source.to_string()) + .user_id(user_id) + .fields(fields) + .with_base(base); + + self.log_server_entry(server_entry).await + } + + /// Write log + /// This function writes log messages. + /// # Parameters + /// - `message`: Message to be logged + /// - `source`: Source of the log + /// - `level`: Log level + /// + /// # Returns + /// Result indicating whether the operation was successful + /// + /// # Example + /// ``` + /// use rustfs_obs::Logger; + /// use tracing_core::Level; + /// + /// async fn example(logger: &Logger) { + /// let _ = logger.write("This is an information message", "example", Level::INFO).await; + /// } + /// ``` + pub async fn write(&self, message: &str, source: &str, level: Level) -> Result<(), LogError> { + self.write_with_context(message, source, level, None, None, Vec::new()).await + } + + /// Shutdown the logger + /// This function shuts down the logger. + /// + /// # Returns + /// Result indicating whether the operation was successful + /// + /// # Example + /// ``` + /// use rustfs_obs::Logger; + /// + /// async fn example(logger: Logger) { + /// let _ = logger.shutdown().await; + /// } + /// ``` + pub async fn shutdown(self) -> Result<(), LogError> { + drop(self.sender); //Close the sending end so that the receiver knows that there is no new message + Ok(()) + } +} + +/// Log error type +/// This enum defines the error types that can occur when logging. +/// It is used to provide more detailed error information. +/// # Example +/// ``` +/// use rustfs_obs::LogError; +/// use thiserror::Error; +/// +/// LogError::SendFailed("Failed to send log"); +/// LogError::Timeout("Operation timed out"); +/// ``` +#[derive(Debug, thiserror::Error)] +pub enum LogError { + #[error("Failed to send log: {0}")] + SendFailed(&'static str), + #[error("Operation timed out: {0}")] + Timeout(&'static str), +} + +/// Start the log module +/// This function starts the log module. +/// It initializes the logger and starts the worker to process logs. +/// # Parameters +/// - `config`: Configuration information +/// - `sinks`: A vector of Sink instances +/// # Returns +/// The global logger instance +/// # Example +/// ``` +/// use rustfs_obs::{AppConfig, start_logger}; +/// +/// let config = AppConfig::default(); +/// let sinks = vec![]; +/// let logger = start_logger(&config, sinks); +/// ``` +pub fn start_logger(config: &AppConfig, sinks: Vec>) -> Logger { + let (logger, receiver) = Logger::new(config); + tokio::spawn(crate::worker::start_worker(receiver, sinks)); + logger +} + +/// Initialize the global logger instance +/// This function initializes the global logger instance and returns a reference to it. +/// If the logger has been initialized before, it will return the existing logger instance. +/// +/// # Parameters +/// - `config`: Configuration information +/// - `sinks`: A vector of Sink instances +/// +/// # Returns +/// A reference to the global logger instance +/// +/// # Example +/// ``` +/// use rustfs_obs::{AppConfig,init_global_logger}; +/// +/// let config = AppConfig::default(); +/// let sinks = vec![]; +/// let logger = init_global_logger(&config, sinks); +/// ``` +pub async fn init_global_logger(config: &AppConfig, sinks: Vec>) -> Arc> { + let logger = Arc::new(Mutex::new(start_logger(config, sinks))); + GLOBAL_LOGGER.set(logger.clone()).expect("Logger already initialized"); + logger +} + +/// Get the global logger instance +/// +/// This function returns a reference to the global logger instance. +/// +/// # Returns +/// A reference to the global logger instance +/// +/// # Example +/// ``` +/// use rustfs_obs::get_global_logger; +/// +/// let logger = get_global_logger(); +/// ``` +pub fn get_global_logger() -> &'static Arc> { + GLOBAL_LOGGER.get().expect("Logger not initialized") +} + +/// Get the global logger instance with a lock +/// This function returns a reference to the global logger instance with a lock. +/// It is used to ensure that the logger is thread-safe. +/// +/// # Returns +/// A reference to the global logger instance with a lock +/// +/// # Example +/// ``` +/// use rustfs_obs::locked_logger; +/// +/// async fn example() { +/// let logger = locked_logger().await; +/// } +/// ``` +pub async fn locked_logger() -> tokio::sync::MutexGuard<'static, Logger> { + get_global_logger().lock().await +} + +/// Initialize with default empty logger if needed (optional) +/// This function initializes the logger with a default empty logger if needed. +/// It is used to ensure that the logger is initialized before logging. +/// +/// # Returns +/// A reference to the global logger instance +/// +/// # Example +/// ``` +/// use rustfs_obs::ensure_logger_initialized; +/// +/// let logger = ensure_logger_initialized(); +/// ``` +pub fn ensure_logger_initialized() -> &'static Arc> { + if GLOBAL_LOGGER.get().is_none() { + let config = AppConfig::default(); + let sinks = vec![]; + let logger = Arc::new(Mutex::new(start_logger(&config, sinks))); + let _ = GLOBAL_LOGGER.set(logger); + } + GLOBAL_LOGGER.get().unwrap() +} + +/// Log information +/// This function logs information messages. +/// +/// # Parameters +/// - `message`: Message to be logged +/// - `source`: Source of the log +/// +/// # Returns +/// Result indicating whether the operation was successful +/// +/// # Example +/// ``` +/// use rustfs_obs::log_info; +/// +/// async fn example() { +/// let _ = log_info("This is an information message", "example").await; +/// } +/// ``` +pub async fn log_info(message: &str, source: &str) -> Result<(), LogError> { + get_global_logger().lock().await.write(message, source, Level::INFO).await +} + +/// Log error +/// This function logs error messages. +/// # Parameters +/// - `message`: Message to be logged +/// - `source`: Source of the log +/// # Returns +/// Result indicating whether the operation was successful +/// # Example +/// ``` +/// use rustfs_obs::log_error; +/// +/// async fn example() { +/// let _ = log_error("This is an error message", "example").await; +/// } +pub async fn log_error(message: &str, source: &str) -> Result<(), LogError> { + get_global_logger().lock().await.write(message, source, Level::ERROR).await +} + +/// Log warning +/// This function logs warning messages. +/// # Parameters +/// - `message`: Message to be logged +/// - `source`: Source of the log +/// # Returns +/// Result indicating whether the operation was successful +/// +/// # Example +/// ``` +/// use rustfs_obs::log_warn; +/// +/// async fn example() { +/// let _ = log_warn("This is a warning message", "example").await; +/// } +/// ``` +pub async fn log_warn(message: &str, source: &str) -> Result<(), LogError> { + get_global_logger().lock().await.write(message, source, Level::WARN).await +} + +/// Log debug +/// This function logs debug messages. +/// # Parameters +/// - `message`: Message to be logged +/// - `source`: Source of the log +/// # Returns +/// Result indicating whether the operation was successful +/// +/// # Example +/// ``` +/// use rustfs_obs::log_debug; +/// +/// async fn example() { +/// let _ = log_debug("This is a debug message", "example").await; +/// } +/// ``` +pub async fn log_debug(message: &str, source: &str) -> Result<(), LogError> { + get_global_logger().lock().await.write(message, source, Level::DEBUG).await +} + +/// Log trace +/// This function logs trace messages. +/// # Parameters +/// - `message`: Message to be logged +/// - `source`: Source of the log +/// +/// # Returns +/// Result indicating whether the operation was successful +/// +/// # Example +/// ``` +/// use rustfs_obs::log_trace; +/// +/// async fn example() { +/// let _ = log_trace("This is a trace message", "example").await; +/// } +/// ``` +pub async fn log_trace(message: &str, source: &str) -> Result<(), LogError> { + get_global_logger().lock().await.write(message, source, Level::TRACE).await +} + +/// Log with context information +/// This function logs messages with context information. +/// # Parameters +/// - `message`: Message to be logged +/// - `source`: Source of the log +/// - `level`: Log level +/// - `request_id`: Request ID +/// - `user_id`: User ID +/// - `fields`: Additional fields +/// # Returns +/// Result indicating whether the operation was successful +/// # Example +/// ``` +/// use tracing_core::Level; +/// use rustfs_obs::log_with_context; +/// +/// async fn example() { +/// let _ = log_with_context("This is an information message", "example", Level::INFO, Some("req-12345".to_string()), Some("user-6789".to_string()), vec![("endpoint".to_string(), "/api/v1/data".to_string())]).await; +/// } +/// ``` +pub async fn log_with_context( + message: &str, + source: &str, + level: Level, + request_id: Option, + user_id: Option, + fields: Vec<(String, String)>, +) -> Result<(), LogError> { + get_global_logger() + .lock() + .await + .write_with_context(message, source, level, request_id, user_id, fields) + .await +} + +/// Log initialization status +#[derive(Debug)] +pub struct InitLogStatus { + pub timestamp: SystemTime, + pub service_name: String, + pub version: String, + pub environment: String, +} + +impl Default for InitLogStatus { + fn default() -> Self { + Self { + timestamp: SystemTime::now(), + service_name: String::from(SERVICE_NAME), + version: SERVICE_VERSION.to_string(), + environment: ENVIRONMENT.to_string(), + } + } +} + +impl InitLogStatus { + pub fn new_config(config: &OtelConfig) -> Self { + let config = config.clone(); + let environment = config.environment.unwrap_or(ENVIRONMENT.to_string()); + let version = config.service_version.unwrap_or(SERVICE_VERSION.to_string()); + Self { + timestamp: SystemTime::now(), + service_name: String::from(SERVICE_NAME), + version, + environment, + } + } + + pub async fn init_start_log(config: &OtelConfig) -> Result<(), LogError> { + let status = Self::new_config(config); + log_init_state(Some(status)).await + } +} + +/// Log initialization details during system startup +pub async fn log_init_state(status: Option) -> Result<(), LogError> { + let status = status.unwrap_or_default(); + + let base_entry = BaseLogEntry::new() + .timestamp(chrono::DateTime::from(status.timestamp)) + .message(Some(format!( + "Service initialization started - {} v{} in {}", + status.service_name, status.version, status.environment + ))) + .request_id(Some("system_init".to_string())); + + let server_entry = ServerLogEntry::new(Level::INFO, "system_initialization".to_string()) + .with_base(base_entry) + .user_id(Some("system".to_string())); + + get_global_logger().lock().await.log_server_entry(server_entry).await?; + Ok(()) +} diff --git a/packages/obs/src/sink.rs b/packages/obs/src/sink.rs new file mode 100644 index 000000000..4a44f0ea4 --- /dev/null +++ b/packages/obs/src/sink.rs @@ -0,0 +1,466 @@ +use crate::{AppConfig, LogRecord, UnifiedLogEntry}; +use async_trait::async_trait; +use std::sync::Arc; +use tokio::fs::OpenOptions; +use tokio::io; +use tokio::io::AsyncWriteExt; +use tracing::debug; + +/// Sink Trait definition, asynchronously write logs +#[async_trait] +pub trait Sink: Send + Sync { + async fn write(&self, entry: &UnifiedLogEntry); +} + +#[cfg(feature = "kafka")] +/// Kafka Sink Implementation +pub struct KafkaSink { + producer: rdkafka::producer::FutureProducer, + topic: String, + batch_size: usize, + batch_timeout_ms: u64, + entries: Arc>>, + last_flush: Arc, +} + +#[cfg(feature = "kafka")] +impl KafkaSink { + /// Create a new KafkaSink instance + pub fn new(producer: rdkafka::producer::FutureProducer, topic: String, batch_size: usize, batch_timeout_ms: u64) -> Self { + // Create Arc-wrapped values first + let entries = Arc::new(tokio::sync::Mutex::new(Vec::with_capacity(batch_size))); + let last_flush = Arc::new(std::sync::atomic::AtomicU64::new( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64, + )); + let sink = KafkaSink { + producer: producer.clone(), + topic: topic.clone(), + batch_size, + batch_timeout_ms, + entries: entries.clone(), + last_flush: last_flush.clone(), + }; + + // Start background flusher + tokio::spawn(Self::periodic_flush(producer, topic, entries, last_flush, batch_timeout_ms)); + + sink + } + + /// Add a getter method to read the batch_timeout_ms field + #[allow(dead_code)] + pub fn batch_timeout(&self) -> u64 { + self.batch_timeout_ms + } + + /// Add a method to dynamically adjust the timeout if needed + #[allow(dead_code)] + pub fn set_batch_timeout(&mut self, new_timeout_ms: u64) { + self.batch_timeout_ms = new_timeout_ms; + } + + async fn periodic_flush( + producer: rdkafka::producer::FutureProducer, + topic: String, + entries: Arc>>, + last_flush: Arc, + timeout_ms: u64, + ) { + loop { + tokio::time::sleep(tokio::time::Duration::from_millis(timeout_ms / 2)).await; + + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + + let last = last_flush.load(std::sync::atomic::Ordering::Relaxed); + + if now - last >= timeout_ms { + let mut batch = entries.lock().await; + if !batch.is_empty() { + Self::send_batch(&producer, &topic, batch.drain(..).collect()).await; + last_flush.store(now, std::sync::atomic::Ordering::Relaxed); + } + } + } + } + + async fn send_batch(producer: &rdkafka::producer::FutureProducer, topic: &str, entries: Vec) { + for entry in entries { + let payload = match serde_json::to_string(&entry) { + Ok(p) => p, + Err(e) => { + eprintln!("Failed to serialize log entry: {}", e); + continue; + } + }; + + let span_id = entry.get_timestamp().to_rfc3339(); + + let _ = producer + .send( + rdkafka::producer::FutureRecord::to(topic).payload(&payload).key(&span_id), + std::time::Duration::from_secs(5), + ) + .await; + } + } +} + +#[cfg(feature = "kafka")] +#[async_trait] +impl Sink for KafkaSink { + async fn write(&self, entry: &UnifiedLogEntry) { + let mut batch = self.entries.lock().await; + batch.push(entry.clone()); + + let should_flush_by_size = batch.len() >= self.batch_size; + let should_flush_by_time = { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + let last = self.last_flush.load(std::sync::atomic::Ordering::Relaxed); + now - last >= self.batch_timeout_ms + }; + + if should_flush_by_size || should_flush_by_time { + // Existing flush logic + let entries_to_send: Vec = batch.drain(..).collect(); + let producer = self.producer.clone(); + let topic = self.topic.clone(); + + self.last_flush.store( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64, + std::sync::atomic::Ordering::Relaxed, + ); + + tokio::spawn(async move { + KafkaSink::send_batch(&producer, &topic, entries_to_send).await; + }); + } + } +} + +#[cfg(feature = "kafka")] +impl Drop for KafkaSink { + fn drop(&mut self) { + // Perform any necessary cleanup here + // For example, you might want to flush any remaining entries + let producer = self.producer.clone(); + let topic = self.topic.clone(); + let entries = self.entries.clone(); + let last_flush = self.last_flush.clone(); + + tokio::spawn(async move { + let mut batch = entries.lock().await; + if !batch.is_empty() { + KafkaSink::send_batch(&producer, &topic, batch.drain(..).collect()).await; + last_flush.store( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64, + std::sync::atomic::Ordering::Relaxed, + ); + } + }); + + eprintln!("Dropping KafkaSink with topic: {}", self.topic); + } +} + +#[cfg(feature = "webhook")] +/// Webhook Sink Implementation +pub struct WebhookSink { + endpoint: String, + auth_token: String, + client: reqwest::Client, + max_retries: usize, + retry_delay_ms: u64, +} + +#[cfg(feature = "webhook")] +impl WebhookSink { + pub fn new(endpoint: String, auth_token: String, max_retries: usize, retry_delay_ms: u64) -> Self { + WebhookSink { + endpoint, + auth_token, + client: reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(10)) + .build() + .unwrap_or_else(|_| reqwest::Client::new()), + max_retries, + retry_delay_ms, + } + } +} + +#[cfg(feature = "webhook")] +#[async_trait] +impl Sink for WebhookSink { + async fn write(&self, entry: &UnifiedLogEntry) { + let mut retries = 0; + let url = self.endpoint.clone(); + let entry_clone = entry.clone(); + let auth_value = reqwest::header::HeaderValue::from_str(format!("Bearer {}", self.auth_token.clone()).as_str()).unwrap(); + while retries < self.max_retries { + match self + .client + .post(&url) + .header(reqwest::header::AUTHORIZATION, auth_value.clone()) + .json(&entry_clone) + .send() + .await + { + Ok(response) if response.status().is_success() => { + return; + } + _ => { + retries += 1; + if retries < self.max_retries { + tokio::time::sleep(tokio::time::Duration::from_millis( + self.retry_delay_ms * (1 << retries), // Exponential backoff + )) + .await; + } + } + } + } + + eprintln!("Failed to send log to webhook after {} retries", self.max_retries); + } +} + +#[cfg(feature = "webhook")] +impl Drop for WebhookSink { + fn drop(&mut self) { + // Perform any necessary cleanup here + // For example, you might want to log that the sink is being dropped + eprintln!("Dropping WebhookSink with URL: {}", self.endpoint); + } +} + +#[cfg(feature = "file")] +/// File Sink Implementation +pub struct FileSink { + path: String, + buffer_size: usize, + writer: Arc>>, + entry_count: std::sync::atomic::AtomicUsize, + last_flush: std::sync::atomic::AtomicU64, + flush_interval_ms: u64, // Time between flushes + flush_threshold: usize, // Number of entries before flush +} + +#[cfg(feature = "file")] +impl FileSink { + /// Create a new FileSink instance + pub async fn new( + path: String, + buffer_size: usize, + flush_interval_ms: u64, + flush_threshold: usize, + ) -> Result { + // check if the file exists + let file_exists = tokio::fs::metadata(&path).await.is_ok(); + // if the file not exists, create it + if !file_exists { + tokio::fs::create_dir_all(std::path::Path::new(&path).parent().unwrap()).await?; + debug!("the file not exists,create if. path: {:?}", path) + } + let file = if file_exists { + // If the file exists, open it in append mode + debug!("FileSink: File exists, opening in append mode."); + OpenOptions::new().append(true).create(true).open(&path).await? + } else { + // If the file does not exist, create it + debug!("FileSink: File does not exist, creating a new file."); + // Create the file and write a header or initial content if needed + OpenOptions::new().create(true).write(true).open(&path).await? + }; + let writer = io::BufWriter::with_capacity(buffer_size, file); + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + Ok(FileSink { + path, + buffer_size, + writer: Arc::new(tokio::sync::Mutex::new(writer)), + entry_count: std::sync::atomic::AtomicUsize::new(0), + last_flush: std::sync::atomic::AtomicU64::new(now), + flush_interval_ms, + flush_threshold, + }) + } + + #[allow(dead_code)] + async fn initialize_writer(&mut self) -> io::Result<()> { + let file = tokio::fs::File::create(&self.path).await?; + + // Use buffer_size to create a buffer writer with a specified capacity + let buf_writer = io::BufWriter::with_capacity(self.buffer_size, file); + + // Replace the original writer with the new Mutex + self.writer = Arc::new(tokio::sync::Mutex::new(buf_writer)); + Ok(()) + } + + // Get the current buffer size + #[allow(dead_code)] + pub fn buffer_size(&self) -> usize { + self.buffer_size + } + + // How to dynamically adjust the buffer size + #[allow(dead_code)] + pub async fn set_buffer_size(&mut self, new_size: usize) -> io::Result<()> { + if self.buffer_size != new_size { + self.buffer_size = new_size; + // Reinitialize the writer directly, without checking is_some() + self.initialize_writer().await?; + } + Ok(()) + } + + // Check if flushing is needed based on count or time + fn should_flush(&self) -> bool { + // Check entry count threshold + if self.entry_count.load(std::sync::atomic::Ordering::Relaxed) >= self.flush_threshold { + return true; + } + + // Check time threshold + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + + let last = self.last_flush.load(std::sync::atomic::Ordering::Relaxed); + now - last >= self.flush_interval_ms + } +} + +#[cfg(feature = "file")] +#[async_trait] +impl Sink for FileSink { + async fn write(&self, entry: &UnifiedLogEntry) { + let line = format!("{:?}\n", entry); + let mut writer = self.writer.lock().await; + + if let Err(e) = writer.write_all(line.as_bytes()).await { + eprintln!( + "Failed to write log to file {}: {},entry timestamp:{:?}", + self.path, + e, + entry.get_timestamp() + ); + return; + } + + // Only flush periodically to improve performance + // Logic to determine when to flush could be added here + // Increment the entry count + self.entry_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + + // Check if we should flush + if self.should_flush() { + if let Err(e) = writer.flush().await { + eprintln!("Failed to flush log file {}: {}", self.path, e); + return; + } + + // Reset counters + self.entry_count.store(0, std::sync::atomic::Ordering::Relaxed); + + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + + self.last_flush.store(now, std::sync::atomic::Ordering::Relaxed); + } + } +} + +#[cfg(feature = "file")] +impl Drop for FileSink { + fn drop(&mut self) { + let writer = self.writer.clone(); + let path = self.path.clone(); + + tokio::task::spawn_blocking(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + let mut writer = writer.lock().await; + if let Err(e) = writer.flush().await { + eprintln!("Failed to flush log file {}: {}", path, e); + } + }); + }); + } +} + +/// Create a list of Sink instances +pub async fn create_sinks(config: &AppConfig) -> Vec> { + let mut sinks: Vec> = Vec::new(); + + #[cfg(feature = "kafka")] + if config.sinks.kafka.enabled { + match rdkafka::config::ClientConfig::new() + .set("bootstrap.servers", &config.sinks.kafka.bootstrap_servers) + .set("message.timeout.ms", "5000") + .create() + { + Ok(producer) => { + sinks.push(Arc::new(KafkaSink::new( + producer, + config.sinks.kafka.topic.clone(), + config.sinks.kafka.batch_size.unwrap_or(100), + config.sinks.kafka.batch_timeout_ms.unwrap_or(1000), + ))); + } + Err(e) => eprintln!("Failed to create Kafka producer: {}", e), + } + } + + #[cfg(feature = "webhook")] + if config.sinks.webhook.enabled { + sinks.push(Arc::new(WebhookSink::new( + config.sinks.webhook.endpoint.clone(), + config.sinks.webhook.auth_token.clone(), + config.sinks.webhook.max_retries.unwrap_or(3), + config.sinks.webhook.retry_delay_ms.unwrap_or(100), + ))); + } + + #[cfg(feature = "file")] + { + let path = if config.sinks.file.enabled { + config.sinks.file.path.clone() + } else { + "default.log".to_string() + }; + debug!("FileSink: Using path: {}", path); + sinks.push(Arc::new( + FileSink::new( + path.clone(), + config.sinks.file.buffer_size.unwrap_or(8192), + config.sinks.file.flush_interval_ms.unwrap_or(1000), + config.sinks.file.flush_threshold.unwrap_or(100), + ) + .await + .unwrap(), + )); + } + + sinks +} diff --git a/packages/obs/src/telemetry.rs b/packages/obs/src/telemetry.rs new file mode 100644 index 000000000..f73d9b879 --- /dev/null +++ b/packages/obs/src/telemetry.rs @@ -0,0 +1,316 @@ +use crate::global::{ENVIRONMENT, LOGGER_LEVEL, METER_INTERVAL, SAMPLE_RATIO, SERVICE_NAME, SERVICE_VERSION, USE_STDOUT}; +use crate::{get_local_ip_with_default, OtelConfig}; +use opentelemetry::trace::TracerProvider; +use opentelemetry::{global, KeyValue}; +use opentelemetry_appender_tracing::layer; +use opentelemetry_otlp::WithExportConfig; +use opentelemetry_sdk::logs::SdkLoggerProvider; +use opentelemetry_sdk::{ + metrics::{MeterProviderBuilder, PeriodicReader, SdkMeterProvider}, + trace::{RandomIdGenerator, Sampler, SdkTracerProvider}, + Resource, +}; +use opentelemetry_semantic_conventions::{ + attribute::{DEPLOYMENT_ENVIRONMENT_NAME, NETWORK_LOCAL_ADDRESS, SERVICE_VERSION as OTEL_SERVICE_VERSION}, + SCHEMA_URL, +}; +use prometheus::{Encoder, Registry, TextEncoder}; +use std::io::IsTerminal; +use std::sync::Arc; +use tokio::sync::{Mutex, OnceCell}; +use tracing::{info, warn}; +use tracing_error::ErrorLayer; +use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer}; + +/// A guard object that manages the lifecycle of OpenTelemetry components. +/// +/// This struct holds references to the created OpenTelemetry providers and ensures +/// they are properly shut down when the guard is dropped. It implements the RAII +/// (Resource Acquisition Is Initialization) pattern for managing telemetry resources. +/// +/// When this guard goes out of scope, it will automatically shut down: +/// - The tracer provider (for distributed tracing) +/// - The meter provider (for metrics collection) +/// - The logger provider (for structured logging) +/// +/// # Example +/// +/// ``` +/// use rustfs_obs::{init_telemetry, OtelConfig}; +/// +/// let config = OtelConfig::default(); +/// let otel_guard = init_telemetry(&config); +/// +/// // The guard is kept alive for the duration of the application +/// // When it's dropped, all telemetry components are properly shut down +/// drop(otel_guard); +/// ``` +#[derive(Debug)] +pub struct OtelGuard { + tracer_provider: SdkTracerProvider, + meter_provider: SdkMeterProvider, + logger_provider: SdkLoggerProvider, +} + +impl Drop for OtelGuard { + fn drop(&mut self) { + if let Err(err) = self.tracer_provider.shutdown() { + eprintln!("Tracer shutdown error: {:?}", err); + } + if let Err(err) = self.meter_provider.shutdown() { + eprintln!("Meter shutdown error: {:?}", err); + } + if let Err(err) = self.logger_provider.shutdown() { + eprintln!("Logger shutdown error: {:?}", err); + } + } +} + +/// Global registry for Prometheus metrics +static GLOBAL_REGISTRY: OnceCell>> = OnceCell::const_new(); + +/// Get the global registry instance +/// This function returns a reference to the global registry instance. +/// +/// # Returns +/// A reference to the global registry instance +/// +/// # Example +/// ``` +/// use rustfs_obs::get_global_registry; +/// +/// let registry = get_global_registry(); +/// ``` +pub fn get_global_registry() -> Arc> { + GLOBAL_REGISTRY.get().unwrap().clone() +} + +/// Prometheus metric endpoints +/// This function returns a string containing the Prometheus metrics. +/// The metrics are collected from the global registry. +/// The function is used to expose the metrics via an HTTP endpoint. +/// +/// # Returns +/// A string containing the Prometheus metrics +/// +/// # Example +/// ``` +/// use rustfs_obs::metrics; +/// +/// async fn main() { +/// let metrics = metrics().await; +/// println!("{}", metrics); +/// } +/// ``` +pub async fn metrics() -> String { + let encoder = TextEncoder::new(); + // Get a reference to the registry for reading metrics + let registry = get_global_registry().lock().await.to_owned(); + let metric_families = registry.gather(); + if metric_families.is_empty() { + warn!("No metrics available in Prometheus registry"); + } else { + info!("Metrics collected: {} families", metric_families.len()); + } + let mut buffer = Vec::new(); + encoder.encode(&metric_families, &mut buffer).unwrap(); + String::from_utf8(buffer).unwrap_or_else(|_| "Error encoding metrics".to_string()) +} + +/// create OpenTelemetry Resource +fn resource(config: &OtelConfig) -> Resource { + let config = config.clone(); + Resource::builder() + .with_service_name(config.service_name.unwrap_or(SERVICE_NAME.to_string())) + .with_schema_url( + [ + KeyValue::new(OTEL_SERVICE_VERSION, config.service_version.unwrap_or(SERVICE_VERSION.to_string())), + KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, config.environment.unwrap_or(ENVIRONMENT.to_string())), + KeyValue::new(NETWORK_LOCAL_ADDRESS, get_local_ip_with_default()), + ], + SCHEMA_URL, + ) + .build() +} + +/// Creates a periodic reader for stdout metrics +fn create_periodic_reader(interval: u64) -> PeriodicReader { + PeriodicReader::builder(opentelemetry_stdout::MetricExporter::default()) + .with_interval(std::time::Duration::from_secs(interval)) + .build() +} + +/// Initialize Meter Provider +fn init_meter_provider(config: &OtelConfig) -> SdkMeterProvider { + let mut builder = MeterProviderBuilder::default().with_resource(resource(config)); + // If endpoint is empty, use stdout output + if config.endpoint.is_empty() { + builder = builder.with_reader(create_periodic_reader(config.meter_interval.unwrap_or(METER_INTERVAL))); + } else { + // If endpoint is not empty, use otlp output + let exporter = opentelemetry_otlp::MetricExporter::builder() + .with_tonic() + .with_endpoint(&config.endpoint) + .with_temporality(opentelemetry_sdk::metrics::Temporality::default()) + .build() + .unwrap(); + builder = builder.with_reader( + PeriodicReader::builder(exporter) + .with_interval(std::time::Duration::from_secs(config.meter_interval.unwrap_or(METER_INTERVAL))) + .build(), + ); + // If use_stdout is true, output to stdout at the same time + if config.use_stdout.unwrap_or(USE_STDOUT) { + builder = builder.with_reader(create_periodic_reader(config.meter_interval.unwrap_or(METER_INTERVAL))); + } + } + let registry = Registry::new(); + // Set global registry + GLOBAL_REGISTRY.set(Arc::new(Mutex::new(registry.clone()))).unwrap(); + // Create Prometheus exporter + let prometheus_exporter = opentelemetry_prometheus::exporter().with_registry(registry).build().unwrap(); + // Build meter provider + let meter_provider = builder.with_reader(prometheus_exporter).build(); + global::set_meter_provider(meter_provider.clone()); + meter_provider +} + +/// Initialize Tracer Provider +fn init_tracer_provider(config: &OtelConfig) -> SdkTracerProvider { + let sample_ratio = config.sample_ratio.unwrap_or(SAMPLE_RATIO); + let sampler = if sample_ratio > 0.0 && sample_ratio < 1.0 { + Sampler::TraceIdRatioBased(sample_ratio) + } else { + Sampler::AlwaysOn + }; + let builder = SdkTracerProvider::builder() + .with_sampler(sampler) + .with_id_generator(RandomIdGenerator::default()) + .with_resource(resource(config)); + + let tracer_provider = if config.endpoint.is_empty() { + builder + .with_batch_exporter(opentelemetry_stdout::SpanExporter::default()) + .build() + } else { + let exporter = opentelemetry_otlp::SpanExporter::builder() + .with_tonic() + .with_endpoint(&config.endpoint) + .build() + .unwrap(); + if config.use_stdout.unwrap_or(USE_STDOUT) { + builder + .with_batch_exporter(exporter) + .with_batch_exporter(opentelemetry_stdout::SpanExporter::default()) + } else { + builder.with_batch_exporter(exporter) + } + .build() + }; + + global::set_tracer_provider(tracer_provider.clone()); + tracer_provider +} + +/// Initialize Telemetry +pub fn init_telemetry(config: &OtelConfig) -> OtelGuard { + let tracer_provider = init_tracer_provider(config); + let meter_provider = init_meter_provider(config); + + // Initialize logger provider based on configuration + let logger_provider = { + let mut builder = SdkLoggerProvider::builder().with_resource(resource(config)); + + if config.endpoint.is_empty() { + // Use stdout exporter when no endpoint is configured + builder = builder.with_simple_exporter(opentelemetry_stdout::LogExporter::default()); + } else { + // Configure OTLP exporter when endpoint is provided + let exporter = opentelemetry_otlp::LogExporter::builder() + .with_tonic() + .with_endpoint(&config.endpoint) + .build() + .unwrap(); + + builder = builder.with_batch_exporter(exporter); + + // Add stdout exporter if requested + if config.use_stdout.unwrap_or(USE_STDOUT) { + builder = builder.with_batch_exporter(opentelemetry_stdout::LogExporter::default()); + } + } + + builder.build() + }; + let config = config.clone(); + let logger_level = config.logger_level.unwrap_or(LOGGER_LEVEL.to_string()); + let logger_level = logger_level.as_str(); + // Setup OpenTelemetryTracingBridge layer + let otel_layer = { + // Filter to prevent infinite telemetry loops + // This blocks events from OpenTelemetry and its dependent libraries (tonic, reqwest, etc.) + // from being sent back to OpenTelemetry itself + let filter_otel = match logger_level { + "trace" | "debug" => { + info!("OpenTelemetry tracing initialized with level: {}", logger_level); + EnvFilter::new(logger_level) + } + _ => { + let mut filter = EnvFilter::new(logger_level); + for directive in ["hyper", "opentelemetry", "tonic", "h2", "reqwest"] { + filter = filter.add_directive(format!("{}=off", directive).parse().unwrap()); + } + filter + } + }; + + layer::OpenTelemetryTracingBridge::new(&logger_provider).with_filter(filter_otel) + }; + + let tracer = tracer_provider.tracer(config.service_name.unwrap_or(SERVICE_NAME.to_string())); + let registry = tracing_subscriber::registry() + .with(switch_level(logger_level)) + .with(OpenTelemetryLayer::new(tracer)) + .with(MetricsLayer::new(meter_provider.clone())) + .with(otel_layer); + // Configure formatting layer + let enable_color = std::io::stdout().is_terminal(); + let fmt_layer = tracing_subscriber::fmt::layer() + .with_ansi(enable_color) + .with_thread_names(true) + .with_file(true) + .with_line_number(true); + + // Creating a formatting layer with explicit type to avoid type mismatches + let fmt_layer = fmt_layer.with_filter( + EnvFilter::new(logger_level).add_directive( + format!("opentelemetry={}", if config.endpoint.is_empty() { logger_level } else { "off" }) + .parse() + .unwrap(), + ), + ); + + registry.with(ErrorLayer::default()).with(fmt_layer).init(); + if !config.endpoint.is_empty() { + info!("OpenTelemetry telemetry initialized with OTLP endpoint: {}", config.endpoint); + } + + OtelGuard { + tracer_provider, + meter_provider, + logger_provider, + } +} + +/// Switch log level +fn switch_level(logger_level: &str) -> tracing_subscriber::filter::LevelFilter { + match logger_level { + "error" => tracing_subscriber::filter::LevelFilter::ERROR, + "warn" => tracing_subscriber::filter::LevelFilter::WARN, + "info" => tracing_subscriber::filter::LevelFilter::INFO, + "debug" => tracing_subscriber::filter::LevelFilter::DEBUG, + "trace" => tracing_subscriber::filter::LevelFilter::TRACE, + _ => tracing_subscriber::filter::LevelFilter::OFF, + } +} diff --git a/packages/obs/src/utils.rs b/packages/obs/src/utils.rs new file mode 100644 index 000000000..774594577 --- /dev/null +++ b/packages/obs/src/utils.rs @@ -0,0 +1,42 @@ +use local_ip_address::{local_ip, local_ipv6}; +use std::net::{IpAddr, Ipv4Addr}; + +/// Get the IP address of the machine +/// +/// Priority is given to trying to get the IPv4 address, and if it fails, try to get the IPv6 address. +/// If both fail to retrieve, None is returned. +/// +/// # Returns +/// +/// * `Some(IpAddr)` - Native IP address (IPv4 or IPv6) +/// * `None` - Unable to obtain any native IP address +pub fn get_local_ip() -> Option { + local_ip().ok().or_else(|| local_ipv6().ok()) +} + +/// Get the IP address of the machine as a string +/// +/// If the IP address cannot be obtained, returns "127.0.0.1" as the default value. +/// +/// # Returns +/// +/// * `String` - Native IP address (IPv4 or IPv6) as a string, or the default value +pub fn get_local_ip_with_default() -> String { + get_local_ip() + .unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))) // Provide a safe default value + .to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_local_ip() { + match get_local_ip() { + Some(ip) => println!("the ip address of this machine:{}", ip), + None => println!("Unable to obtain the IP address of the machine"), + } + assert!(get_local_ip().is_some()); + } +} diff --git a/packages/obs/src/worker.rs b/packages/obs/src/worker.rs new file mode 100644 index 000000000..2d7ee2e14 --- /dev/null +++ b/packages/obs/src/worker.rs @@ -0,0 +1,13 @@ +use crate::{sink::Sink, UnifiedLogEntry}; +use std::sync::Arc; +use tokio::sync::mpsc::Receiver; + +/// Start the log processing worker thread +pub async fn start_worker(receiver: Receiver, sinks: Vec>) { + let mut receiver = receiver; + while let Some(entry) = receiver.recv().await { + for sink in &sinks { + sink.write(&entry).await; + } + } +} diff --git a/rustfs/Cargo.toml b/rustfs/Cargo.toml index 805406219..acb7be72f 100644 --- a/rustfs/Cargo.toml +++ b/rustfs/Cargo.toml @@ -27,7 +27,7 @@ policy.workspace = true flatbuffers.workspace = true futures.workspace = true futures-util.workspace = true -h2 = "0.4.7" +#h2 = "0.4.7" hyper.workspace = true hyper-util.workspace = true http.workspace = true @@ -35,6 +35,7 @@ http-body.workspace = true lock.workspace = true mime.workspace = true netif.workspace = true +once_cell.workspace = true pin-project-lite.workspace = true prost.workspace = true prost-types.workspace = true @@ -62,6 +63,7 @@ tokio-stream.workspace = true tonic.workspace = true tonic-reflection.workspace = true tower.workspace = true +tracing-core = { workspace = true } tracing-error.workspace = true tracing-subscriber.workspace = true transform-stream.workspace = true @@ -84,6 +86,7 @@ mime_guess = "2.0.5" rust-embed = { workspace = true, features = ["interpolate-folder-path"] } local-ip-address = { workspace = true } chrono = { workspace = true } +rustfs-obs = { workspace = true } [build-dependencies] prost-build.workspace = true diff --git a/rustfs/src/config/mod.rs b/rustfs/src/config/mod.rs index ada5307ef..f955ccc78 100644 --- a/rustfs/src/config/mod.rs +++ b/rustfs/src/config/mod.rs @@ -1,11 +1,32 @@ use clap::Parser; use const_str::concat; use ecstore::global::DEFAULT_PORT; +use std::string::ToString; shadow_rs::shadow!(build); +/// Default Access Key +/// Default value: rustfsadmin +/// Environment variable: RUSTFS_ACCESS_KEY +/// Command line argument: --access-key +/// Example: RUSTFS_ACCESS_KEY=rustfsadmin +/// Example: --access-key rustfsadmin pub const DEFAULT_ACCESS_KEY: &str = "rustfsadmin"; +/// Default Secret Key +/// Default value: rustfsadmin +/// Environment variable: RUSTFS_SECRET_KEY +/// Command line argument: --secret-key +/// Example: RUSTFS_SECRET_KEY=rustfsadmin +/// Example: --secret-key rustfsadmin pub const DEFAULT_SECRET_KEY: &str = "rustfsadmin"; +/// Default configuration file for observability +/// Default value: config/obs.toml +/// Environment variable: RUSTFS_OBS_CONFIG +/// Command line argument: --obs-config +/// Example: RUSTFS_OBS_CONFIG=config/obs.toml +/// Example: --obs-config config/obs.toml +/// Example: --obs-config /etc/rustfs/obs.toml +pub const DEFAULT_OBS_CONFIG: &str = "config/obs.toml"; /// Default TLS key for rustfs /// This is the default key for TLS. @@ -39,7 +60,7 @@ const LONG_VERSION: &str = concat!( concat!("git status :\n", build::GIT_STATUS_FILE), ); -#[derive(Debug, Parser)] +#[derive(Debug, Parser, Clone)] #[command(version = SHORT_VERSION, long_version = LONG_VERSION)] pub struct Opt { /// DIR points to a directory on a filesystem. @@ -71,6 +92,11 @@ pub struct Opt { #[arg(long, default_value_t = format!("127.0.0.1:{}", 9002), env = "RUSTFS_CONSOLE_ADDRESS")] pub console_address: String, + /// Observability configuration file + /// Default value: config/obs.toml + #[arg(long, default_value_t = DEFAULT_OBS_CONFIG.to_string(), env = "RUSTFS_OBS_CONFIG")] + pub obs_config: String, + /// tls path for rustfs api and console. #[arg(long, env = "RUSTFS_TLS_PATH")] pub tls_path: Option, diff --git a/rustfs/src/logging/mod.rs b/rustfs/src/logging/mod.rs new file mode 100644 index 000000000..e69de29bb diff --git a/rustfs/src/main.rs b/rustfs/src/main.rs index 8edc9e72f..318594cbf 100644 --- a/rustfs/src/main.rs +++ b/rustfs/src/main.rs @@ -3,6 +3,7 @@ mod auth; mod config; mod console; mod grpc; +mod logging; mod service; mod storage; mod utils; @@ -35,6 +36,7 @@ use hyper_util::{ }; use iam::init_iam_sys; use protos::proto_gen::node_service::node_service_server::NodeServiceServer; +use rustfs_obs::{init_obs, load_config, set_global_guard, InitLogStatus}; use rustls::ServerConfig; use s3s::{host::MultiDomain, service::S3ServiceBuilder}; use service::hybrid; @@ -44,10 +46,11 @@ use tokio::net::TcpListener; use tokio_rustls::TlsAcceptor; use tonic::{metadata::MetadataValue, Request, Status}; use tower_http::cors::CorsLayer; -use tracing::{debug, error, info, warn}; +use tracing::{debug, error, info, info_span, warn}; use tracing_error::ErrorLayer; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; +#[allow(dead_code)] fn setup_tracing() { use tracing_subscriber::EnvFilter; @@ -87,22 +90,35 @@ fn print_server_info() { info!("Docs: {}", cfg.doc()); } -fn main() -> Result<()> { - //解析获得到的参数 +#[tokio::main] +async fn main() -> Result<()> { + // Parse the obtained parameters let opt = config::Opt::parse(); - //设置 trace - setup_tracing(); + // Load the configuration file + let config = load_config(Some(opt.clone().obs_config)); - //运行参数 - run(opt) + // Initialize Observability + let (_logger, guard) = init_obs(config.clone()).await; + + // Store in global storage + set_global_guard(guard)?; + + // Log initialization status + InitLogStatus::init_start_log(&config.observability).await?; + + // Run parameters + run(opt).await } -#[tokio::main] +// #[tokio::main] async fn run(opt: config::Opt) -> Result<()> { + let span = info_span!("trace-main-run"); + let _enter = span.enter(); + debug!("opt: {:?}", &opt); - let mut server_addr = net::check_local_server_addr(opt.address.as_str()).unwrap(); + let mut server_addr = net::check_local_server_addr(opt.address.as_str())?; if server_addr.port() == 0 { server_addr.set_port(get_available_port()); @@ -115,7 +131,8 @@ async fn run(opt: config::Opt) -> Result<()> { debug!("server_address {}", &server_address); //设置 AK 和 SK - iam::init_global_action_cred(Some(opt.access_key.clone()), Some(opt.secret_key.clone())).unwrap(); + iam::init_global_action_cred(Some(opt.access_key.clone()), Some(opt.secret_key.clone()))?; + set_global_rustfs_port(server_port); //监听地址,端口从参数中获取 @@ -256,7 +273,7 @@ async fn run(opt: config::Opt) -> Result<()> { match res { Ok(conn) => conn, Err(err) => { - tracing::error!("error accepting connection: {err}"); + error!("error accepting connection: {err}"); continue; } } @@ -267,7 +284,13 @@ async fn run(opt: config::Opt) -> Result<()> { }; if has_tls_certs { debug!("TLS certificates found, starting with SIGINT"); - let tls_socket = match tls_acceptor.as_ref().ok_or_else(|| error("TLS not configured".to_string())).unwrap().accept(socket).await { + let tls_socket = match tls_acceptor + .as_ref() + .ok_or_else(|| error("TLS not configured".to_string())) + .unwrap() + .accept(socket) + .await + { Ok(tls_socket) => tls_socket, Err(err) => { error!("TLS handshake failed {}", err);