Skip to content

Commit

Permalink
Distributed tracing (spans) support, scope propagation and `SlackClie…
Browse files Browse the repository at this point in the history
…nt.run_in_session` implementation (#151)

* Auxiliary SlackClient.run_in_session implementation with docs and examples

* Docs updates

* Span/context propagation
  • Loading branch information
abdolence committed Oct 8, 2022
1 parent 59b0996 commit c6b88bc
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 108 deletions.
7 changes: 6 additions & 1 deletion Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "slack-morphism"
version = "1.2.2"
version = "1.3.0"
authors = ["Abdulla Abdurakhmanov <me@abdolence.dev>"]
edition = "2021"
license = "Apache-2.0"
Expand Down Expand Up @@ -72,6 +72,11 @@ name = "client"
path = "examples/client.rs"
required-features = ["hyper"]

[[example]]
name = "client_with_tracing"
path = "examples/client_with_tracing.rs"
required-features = ["hyper"]

[[example]]
name = "events_api_server"
path = "examples/events_api_server.rs"
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Expand Up @@ -8,6 +8,7 @@
- [Send webhook messages](./send-webhooks-messages.md)
- [Hyper connection types and proxy support](./hyper-connections-types.md)
- [Rate control and retries](./rate-control-and-retries.md)
- [Observability and tracing](./observability-tracing.md)
- [Events API](./events-api.md)
- [Hyper-based](./events-api-hyper.md)
- [Axum-based](./events-api-axum.md)
Expand Down
32 changes: 32 additions & 0 deletions docs/src/observability-tracing.md
@@ -0,0 +1,32 @@
# Observability and tracing

The library uses popular `tracing` crate for logs and distributed traces (spans).
To improve observability for your specific cases, additionally to the fields provided by library, you can inject your own trace fields:

```rust,noplaypen
use slack_morphism::prelude::*;
use tracing::*;
// While Team ID is optional but still useful for tracing and rate control purposes
let token: SlackApiToken =
SlackApiToken::new(token_value).with_team_id(config_env_var("SLACK_TEST_TEAM_ID")?.into());
// Sessions are lightweight and basically just a reference to client and token
let my_custom_span = span!(Level::DEBUG, "My scope", my_scope_attr = "my-scope-value");
debug!("Testing tracing abilities");
client
.run_in_session(&token, |session| async move {
let test: SlackApiTestResponse = session
.api_test(&SlackApiTestRequest::new().with_foo("Test".into()))
.await?;
println!("{:#?}", test);
let auth_test = session.auth_test().await?;
println!("{:#?}", auth_test);
Ok(())
})
.instrument(my_custom_span.or_current())
.await
```
24 changes: 24 additions & 0 deletions docs/src/web-api.md
Expand Up @@ -52,3 +52,27 @@ async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Ok(())
}
```

Note that `session` is just an auxiliary lightweight structure that stores references to the token and the client
to make easier to have series of calls for the same token.
It doesn't make any network calls. There is no need to store it.

Another option is to use `session` is to use function `run_in_session`:

```rust,noplaypen
// Sessions are lightweight and basically just a reference to client and token
client
.run_in_session(&token, |session| async move {
let test: SlackApiTestResponse = session
.api_test(&SlackApiTestRequest::new().with_foo("Test".into()))
.await?;
println!("{:#?}", test);
let auth_test = session.auth_test().await?;
println!("{:#?}", auth_test);
Ok(())
})
.await?;
```
24 changes: 22 additions & 2 deletions examples/client.rs
Expand Up @@ -9,7 +9,7 @@ use url::Url;
use futures::stream::BoxStream;
use futures::TryStreamExt;

async fn test_client() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
async fn test_simple_api_calls() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = SlackClient::new(SlackClientHyperConnector::new());
let token_value: SlackApiTokenValue = config_env_var("SLACK_TEST_TOKEN")?.into();
let token: SlackApiToken = SlackApiToken::new(token_value);
Expand All @@ -27,6 +27,15 @@ async fn test_client() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let auth_test = session.auth_test().await?;
println!("{:#?}", auth_test);

Ok(())
}

async fn test_post_message() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = SlackClient::new(SlackClientHyperConnector::new());
let token_value: SlackApiTokenValue = config_env_var("SLACK_TEST_TOKEN")?.into();
let token: SlackApiToken = SlackApiToken::new(token_value);
let session = client.open_session(&token);

let message = WelcomeMessageTemplateParams::new("".into());

let post_chat_req =
Expand All @@ -35,6 +44,15 @@ async fn test_client() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let post_chat_resp = session.chat_post_message(&post_chat_req).await?;
println!("post chat resp: {:#?}", &post_chat_resp);

Ok(())
}

async fn test_scrolling_user_list() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = SlackClient::new(SlackClientHyperConnector::new());
let token_value: SlackApiTokenValue = config_env_var("SLACK_TEST_TOKEN")?.into();
let token: SlackApiToken = SlackApiToken::new(token_value);
let session = client.open_session(&token);

let scroller_req: SlackApiUsersListRequest = SlackApiUsersListRequest::new().with_limit(1);
let scroller = scroller_req.scroller();

Expand Down Expand Up @@ -156,7 +174,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.finish();
tracing::subscriber::set_global_default(subscriber)?;

test_client().await?;
test_simple_api_calls().await?;
test_post_message().await?;
test_scrolling_user_list().await?;

Ok(())
}
45 changes: 45 additions & 0 deletions examples/client_with_tracing.rs
@@ -0,0 +1,45 @@
use slack_morphism::prelude::*;
use tracing::*;

async fn test_simple_api_calls_as_predicate() -> Result<(), Box<dyn std::error::Error + Send + Sync>>
{
let client = SlackClient::new(SlackClientHyperConnector::new());
let token_value: SlackApiTokenValue = config_env_var("SLACK_TEST_TOKEN")?.into();
let token: SlackApiToken =
SlackApiToken::new(token_value).with_team_id(config_env_var("SLACK_TEST_TEAM_ID")?.into()); // While Team ID is optional but still useful for tracing and rate control purposes

// Sessions are lightweight and basically just a reference to client and token
let my_custom_span = span!(Level::DEBUG, "My scope", my_scope_attr = "my-scope-value");
debug!("Testing tracing abilities");

client
.run_in_session(&token, |session| async move {
let test: SlackApiTestResponse = session
.api_test(&SlackApiTestRequest::new().with_foo("Test".into()))
.await?;
println!("{:#?}", test);

let auth_test = session.auth_test().await?;
println!("{:#?}", auth_test);

Ok(())
})
.instrument(my_custom_span.or_current())
.await
}

pub fn config_env_var(name: &str) -> Result<String, String> {
std::env::var(name).map_err(|e| format!("{}: {}", name, e))
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let subscriber = tracing_subscriber::fmt()
.with_env_filter("client_with_tracing=debug,slack_morphism=debug")
.finish();
tracing::subscriber::set_global_default(subscriber)?;

test_simple_api_calls_as_predicate().await?;

Ok(())
}
16 changes: 10 additions & 6 deletions src/api/webhook.rs
Expand Up @@ -13,6 +13,7 @@ use crate::ratectl::*;
use crate::SlackClient;
use crate::{ClientResult, SlackClientHttpConnector};
use rvstruct::ValueStruct;
use tracing::*;

impl<SCHC> SlackClient<SCHC>
where
Expand All @@ -26,14 +27,17 @@ where
incoming_webhook_url: &Url,
req: &SlackApiPostWebhookMessageRequest,
) -> ClientResult<SlackApiPostWebhookMessageResponse> {
let http_webhook_span = span!(Level::DEBUG, "Slack WebHook");

let context = crate::SlackClientApiCallContext {
rate_control_params: Some(&POST_WEBHOOK_SPECIAL_LIMIT_RATE_CTL),
token: None,
tracing_span: &http_webhook_span,
};

self.http_api
.connector
.http_post_uri(
incoming_webhook_url.clone(),
req,
None,
Some(&POST_WEBHOOK_SPECIAL_LIMIT_RATE_CTL),
)
.http_post_uri(incoming_webhook_url.clone(), req, context)
.await
}

Expand Down

0 comments on commit c6b88bc

Please sign in to comment.