feat: wip

This commit is contained in:
Max batleforc 2025-05-20 21:06:53 +02:00
parent 29c5832ed0
commit b3a0187c84
No known key found for this signature in database
GPG Key ID: 25D243AB4B6AC9E7
17 changed files with 3863 additions and 47 deletions

64
.gitignore vendored
View File

@ -45,3 +45,67 @@ Thumbs.db
.nx/cache
.nx/workspace-data
# Created by https://www.toptal.com/developers/gitignore/api/yarn,rust,rust-analyzer,visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=yarn,rust,rust-analyzer,visualstudiocode
### Rust ###
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
### rust-analyzer ###
# Can be generated by other build systems other than cargo (ex: bazelbuild/rust_rules)
rust-project.json
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### yarn ###
# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
.yarn/*
!.yarn/releases
!.yarn/patches
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
# if you are NOT using Zero-installs, then:
# comment the following lines
!.yarn/cache
# and uncomment the following lines
# .pnp.*
# End of https://www.toptal.com/developers/gitignore/api/yarn,rust,rust-analyzer,visualstudiocode
*.node

1274
.pnp.cjs generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,26 @@
{
"recommendations": [
"nrwl.angular-console",
"esbenp.prettier-vscode"
"esbenp.prettier-vscode",
"rust-lang.rust-analyzer",
"github.copilot",
"mitchdenny.ecdc",
"aaron-bond.better-comments",
"bierner.markdown-mermaid",
"hristian-kohler.path-intellisense",
"helixquar.randomeverything",
"shardulm94.trailing-spaces",
"tamasfe.even-better-toml",
"edwinkofler.vscode-hyperupcall-pack-markdown",
"proxzima.sweetdracula",
"wdhongtw.gpg-indicator",
"vadimcn.vscode-lldb",
"naumovs.color-highlight",
"formulahendry.auto-rename-tag",
"PKief.material-icon-theme",
"streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-french",
"usernamehw.errorlens",
"eamodio.gitlens",
"oderwat.indent-rainbow"
]
}

13
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"editor.formatOnSave": true,
"editor.fontFamily": "'FiraCode Nerd Font', 'FiraCode NF','Droid Sans Mono', 'monospace', monospace",
"editor.fontLigatures": true,
"rust-analyzer.linkedProjects": ["./Cargo.toml"],
"editor.tabSize": 2,
"editor.detectIndentation": false,
"workbench.preferredDarkColorTheme": "Sweet Dracula",
"workbench.iconTheme": "material-icon-theme",
"cSpell.language": "en,fr",
"gitlens.plusFeatures.enabled": false,
"editor.guides.bracketPairs": true,
}

909
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,18 @@
[workspace]
resolver = '2'
members = [
'apps/bot',
]
members = ['apps/bot', 'libs/tool_tracing']
[workspace.dependencies]
poise = '0.6.1'
tokio = { version = '1.45.0', features = [
'macros',
'rt-multi-thread',
'io-std',
] }
serde = '1.0'
tracing = '0.1'
serde_json = "1.0"
[profile.release]
lto = true
[workspace.dependencies]
poise = "0.6.1"
tokio = {version = "1.45.0", features = ["rt-multi-thread"]}

View File

@ -7,5 +7,7 @@ edition = "2021"
[dependencies]
poise = { workspace = true }
tokio = { workspace = true }
serde = { workspace = true }
tool_tracing = { path = "../../libs/tool_tracing" }
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

28
apps/bot/src/config.rs Normal file
View File

@ -0,0 +1,28 @@
use poise::serenity_prelude::prelude::TypeMapKey;
use serde::Deserialize;
use tool_tracing::tracing_kind::Tracing;
pub struct ConfigGlobal;
impl TypeMapKey for ConfigGlobal {
type Value = Config;
}
#[derive(Deserialize, Clone)]
pub struct Config {
pub bot_name: String,
pub env: String,
pub port: u16,
pub token: String,
pub prefix: String,
pub tracing: Vec<Tracing>,
}
// Clickhouse https://github.com/ranger-finance/clickhouse-pool/blob/master/examples/simple-clickhouse/src/main.rs
#[derive(Deserialize, Clone)]
pub struct PersistenceConfig {
pub url: String,
pub user: String,
pub password: String,
pub database: String,
}

View File

@ -1,5 +1,6 @@
use poise::serenity_prelude as serenity;
pub mod config;
pub mod dotenv;
struct Data {} // User data, which is stored and accessible in all command invocations
@ -42,4 +43,4 @@ async fn main() {
.framework(framework)
.await;
client.unwrap().start().await.unwrap();
}
}

View File

@ -0,0 +1,30 @@
[package]
name = "tool_tracing"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
serde_repr = "0.1"
tracing = { workspace = true }
tracing-subscriber = { version = "0.3", features = [
"registry",
"env-filter",
"time",
] }
time = "0.3"
tracing-bunyan-formatter = "0.3"
opentelemetry = "0.29"
opentelemetry_sdk = { version = "0.29", features = ["rt-tokio"] }
opentelemetry-otlp = { version = "0.29", features = ["grpc-tonic"] }
tracing-opentelemetry = "0.30"
serde_json = { workspace = true }
tokio = { workspace = true }
tonic = { version = "0.12" }
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(coverage,coverage_nightly)',
] }
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -0,0 +1,43 @@
{
"name": "tool_tracing",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"sourceRoot": "libs/tool_tracing/src",
"targets": {
"build": {
"executor": "@monodon/rust:check",
"outputs": [
"{options.target-dir}"
],
"options": {
"target-dir": "dist/target/tool_tracing"
}
},
"test": {
"cache": true,
"executor": "@monodon/rust:test",
"outputs": [
"{options.target-dir}"
],
"options": {
"target-dir": "dist/target/tool_tracing"
},
"configurations": {
"production": {
"release": true
}
}
},
"lint": {
"cache": true,
"executor": "@monodon/rust:lint",
"outputs": [
"{options.target-dir}"
],
"options": {
"target-dir": "dist/target/tool_tracing"
}
}
},
"tags": []
}

View File

@ -0,0 +1,160 @@
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
use super::tracing_kind::{Tracing, TracingKind};
use opentelemetry::trace::TracerProvider;
use opentelemetry::KeyValue;
use opentelemetry_otlp::{WithExportConfig, WithTonicConfig};
use opentelemetry_sdk::trace;
use opentelemetry_sdk::Resource;
use std::env;
use std::str::FromStr;
use std::{fs::File, sync::Arc, vec};
use time::format_description;
use tonic::metadata::{MetadataMap, MetadataValue};
use tracing::level_filters::LevelFilter;
use tracing::subscriber;
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
use tracing_subscriber::filter::Directive;
use tracing_subscriber::fmt::time::UtcTime;
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
use tracing_subscriber::{fmt, EnvFilter, Layer, Registry};
#[cfg_attr(coverage_nightly, coverage(off))]
pub fn init_tracing(tracing_config: Vec<Tracing>, name: String) {
let mut layers = vec![];
for config in tracing_config {
match config.kind {
TracingKind::File => {
let file = File::options()
.create(true)
.truncate(false)
.append(true)
.open("trace.log")
.expect("Failed to create trace.log");
let formating_layer =
BunyanFormattingLayer::new(name.clone(), Arc::new(file)).boxed();
layers.push(JsonStorageLayer.boxed());
layers.push(formating_layer);
}
TracingKind::Console => {
let time_format = format_description::parse("[hour]:[minute]:[second]")
.expect("format string should be valid!");
let timer = UtcTime::new(time_format);
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::from(config.level).into())
.from_env()
.unwrap()
.add_directive("serenity=error".parse().unwrap());
let terminal_out = fmt::layer()
.with_thread_names(true)
.with_timer(timer)
.with_target(false)
.with_filter(env_filter)
.boxed();
layers.push(terminal_out);
}
TracingKind::Otel => {
let endpoint = match config.additional.get("endpoint") {
Some(endpoint) => endpoint.to_string(),
None => "http://localhost:4317".to_string(),
};
let endpoint_from_env = env::var(format!(
"{}_OTEL_EXPORTER_OTLP_ENDPOINT",
config.name.to_uppercase()
))
.unwrap_or(endpoint);
let pod_name =
std::env::var("POD_NAME").unwrap_or_else(|_| "not_a_pod".to_string());
println!(
"Connecting to endpoint: {} with ENV {}_OTEL_EXPORTER_OTLP_ENDPOINT",
endpoint_from_env.clone(),
config.name.to_uppercase()
);
let mut metadata = MetadataMap::new();
metadata.insert(
"service.name",
MetadataValue::from_str(&name.clone()).unwrap(),
);
metadata.insert("service.pod", MetadataValue::from_str(&pod_name).unwrap());
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(endpoint_from_env)
.with_metadata(metadata)
.build()
.expect("Failed to build exporter");
let trace_provider = trace::SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(
Resource::builder()
.with_service_name(name.clone())
.with_attribute(KeyValue::new("service.pod", pod_name.clone()))
.build(),
)
.build();
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::from(config.level).into())
.from_env()
.unwrap()
.add_directive(Directive::from_str("serenity=error").unwrap());
let telemetry = tracing_opentelemetry::layer()
.with_tracer(trace_provider.tracer(name.clone()))
.with_filter(env_filter);
layers.push(telemetry.boxed());
}
}
}
subscriber::set_global_default(Registry::default().with(layers))
.expect("setting default subscriber failed");
}
#[cfg_attr(coverage_nightly, coverage(off))]
pub fn stop_tracing(tracing_config: Vec<Tracing>, _name: String) {
if tracing_config.iter().any(|x| x.kind == TracingKind::Otel) {}
}
#[cfg(test)]
mod tests {
use crate::level::VerboseLevel;
use super::*;
use std::fs::remove_file;
#[tokio::test]
async fn test_init_tracing() {
let mut tracing_config = vec![
Tracing {
kind: TracingKind::File,
level: VerboseLevel::DEBUG,
additional: Default::default(),
name: "test1".to_string(),
},
Tracing {
kind: TracingKind::Console,
level: VerboseLevel::INFO,
additional: Default::default(),
name: "test2".to_string(),
},
Tracing {
kind: TracingKind::Otel,
level: VerboseLevel::DEBUG,
additional: Default::default(),
name: "test3".to_string(),
},
];
tracing_config[2]
.additional
.insert("endpoint".to_string(), "http://localhost:4317".to_string());
init_tracing(tracing_config.clone(), "test".to_string());
tracing::info!("test part of test_init_tracing");
tracing::error!("test part of test_init_tracing");
tokio::spawn(async {
stop_tracing(tracing_config, "test".to_string());
});
remove_file("trace.log").unwrap();
}
}

View File

@ -0,0 +1,149 @@
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::fmt::{Debug, Display};
use tracing::Level;
#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum VerboseLevel {
ERROR = 4,
WARN = 3,
INFO = 2,
DEBUG = 1,
TRACE = 0,
}
impl From<VerboseLevel> for Level {
fn from(level: VerboseLevel) -> Self {
match level {
VerboseLevel::ERROR => Level::ERROR,
VerboseLevel::WARN => Level::WARN,
VerboseLevel::INFO => Level::INFO,
VerboseLevel::DEBUG => Level::DEBUG,
VerboseLevel::TRACE => Level::TRACE,
}
}
}
impl From<VerboseLevel> for tracing_subscriber::filter::LevelFilter {
fn from(level: VerboseLevel) -> Self {
match level {
VerboseLevel::ERROR => tracing_subscriber::filter::LevelFilter::ERROR,
VerboseLevel::WARN => tracing_subscriber::filter::LevelFilter::WARN,
VerboseLevel::INFO => tracing_subscriber::filter::LevelFilter::INFO,
VerboseLevel::DEBUG => tracing_subscriber::filter::LevelFilter::DEBUG,
VerboseLevel::TRACE => tracing_subscriber::filter::LevelFilter::TRACE,
}
}
}
impl Debug for VerboseLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ERROR => write!(f, "ERROR"),
Self::WARN => write!(f, "WARN"),
Self::INFO => write!(f, "INFO"),
Self::DEBUG => write!(f, "DEBUG"),
Self::TRACE => write!(f, "TRACE"),
}
}
}
impl Display for VerboseLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ERROR => write!(f, "ERROR"),
Self::WARN => write!(f, "WARN"),
Self::INFO => write!(f, "INFO"),
Self::DEBUG => write!(f, "DEBUG"),
Self::TRACE => write!(f, "TRACE"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verbose_level() {
let level = VerboseLevel::ERROR;
let serialized = serde_json::to_string(&level).unwrap();
let deserialized: VerboseLevel = serde_json::from_str(&serialized).unwrap();
assert_eq!(level, deserialized);
}
#[test]
fn test_verbose_level_conversion() {
let level = VerboseLevel::ERROR;
let tracing_level: Level = level.into();
let level_filter: tracing_subscriber::filter::LevelFilter = level.into();
assert_eq!(tracing_level, Level::ERROR);
assert_eq!(level_filter, tracing_subscriber::filter::LevelFilter::ERROR);
let level = VerboseLevel::WARN;
let tracing_level: Level = level.into();
let level_filter: tracing_subscriber::filter::LevelFilter = level.into();
assert_eq!(tracing_level, Level::WARN);
assert_eq!(level_filter, tracing_subscriber::filter::LevelFilter::WARN);
let level = VerboseLevel::INFO;
let tracing_level: Level = level.into();
let level_filter: tracing_subscriber::filter::LevelFilter = level.into();
assert_eq!(tracing_level, Level::INFO);
assert_eq!(level_filter, tracing_subscriber::filter::LevelFilter::INFO);
let level = VerboseLevel::DEBUG;
let tracing_level: Level = level.into();
let level_filter: tracing_subscriber::filter::LevelFilter = level.into();
assert_eq!(tracing_level, Level::DEBUG);
assert_eq!(level_filter, tracing_subscriber::filter::LevelFilter::DEBUG);
let level = VerboseLevel::TRACE;
let tracing_level: Level = level.into();
let level_filter: tracing_subscriber::filter::LevelFilter = level.into();
assert_eq!(tracing_level, Level::TRACE);
assert_eq!(level_filter, tracing_subscriber::filter::LevelFilter::TRACE);
}
#[test]
fn test_verbose_level_debug() {
let level = VerboseLevel::ERROR;
assert_eq!(format!("{:?}", level), "ERROR");
let level = VerboseLevel::WARN;
assert_eq!(format!("{:?}", level), "WARN");
let level = VerboseLevel::INFO;
assert_eq!(format!("{:?}", level), "INFO");
let level = VerboseLevel::DEBUG;
assert_eq!(format!("{:?}", level), "DEBUG");
let level = VerboseLevel::TRACE;
assert_eq!(format!("{:?}", level), "TRACE");
}
#[test]
fn test_verbose_level_display() {
let level = VerboseLevel::ERROR;
assert_eq!(format!("{}", level), "ERROR");
let level = VerboseLevel::WARN;
assert_eq!(format!("{}", level), "WARN");
let level = VerboseLevel::INFO;
assert_eq!(format!("{}", level), "INFO");
let level = VerboseLevel::DEBUG;
assert_eq!(format!("{}", level), "DEBUG");
let level = VerboseLevel::TRACE;
assert_eq!(format!("{}", level), "TRACE");
}
}

View File

@ -0,0 +1,3 @@
pub mod init;
pub mod level;
pub mod tracing_kind;

View File

@ -0,0 +1,56 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::level::VerboseLevel;
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub enum TracingKind {
File,
Console,
Otel,
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct Tracing {
pub kind: TracingKind,
pub name: String,
pub level: VerboseLevel,
#[serde(default)]
pub additional: HashMap<String, String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tracing_kind() {
let tracing = Tracing {
kind: TracingKind::File,
name: "file".to_string(),
level: VerboseLevel::DEBUG,
additional: HashMap::new(),
};
let serialized = serde_json::to_string(&tracing).unwrap();
let deserialized: Tracing = serde_json::from_str(&serialized).unwrap();
assert_eq!(tracing, deserialized);
}
#[test]
fn test_tracing_kind_debug() {
let tracing = Tracing {
kind: TracingKind::File,
name: "file".to_string(),
level: VerboseLevel::DEBUG,
additional: HashMap::new(),
};
assert_eq!(
format!("{:?}", tracing),
"Tracing { kind: File, name: \"file\", level: DEBUG, additional: {} }"
);
}
}

View File

@ -10,7 +10,10 @@
"@monodon/rust": "2.3.0"
},
"devDependencies": {
"@napi-rs/cli": "3.0.0-alpha.63",
"@napi-rs/wasm-runtime": "^0.2.4",
"@nx/workspace": "21.0.3",
"emnapi": "^1.1.0",
"nx": "21.0.3"
},
"packageManager": "yarn@4.9.1"

1129
yarn.lock

File diff suppressed because it is too large Load Diff