tracing_gcloud_layer/
google_logger.rs1use std::sync::Arc;
2
3use reqwest::Client;
4use serde::{Deserialize, Serialize};
5use serde_json::{Value, json};
6use thiserror::Error;
7
8use super::gauth::{GAuth, GAuthCredential, GAuthError};
9
10const WRITE_URL: &str = "https://logging.googleapis.com/v2/entries:write";
12const SCOPES: [&str; 1] = ["https://www.googleapis.com/auth/logging.write"];
14
15#[derive(Debug, Clone)]
16pub struct LogContext {
17 pub log_label: Arc<str>,
19 pub project_id: Arc<str>,
21}
22
23pub trait LogMapper: Send + Sync + Clone + Default + 'static {
27 fn map(&self, context: LogContext, entry: Value) -> serde_json::Value
29 where
30 Self: Sized;
31}
32
33#[derive(Debug, Clone)]
35pub struct GoogleLogger<M: LogMapper> {
36 log_context: LogContext,
37 gauth: GAuth,
38 http_client: Client,
39 mapper: M,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct ResponseError {
44 pub error: ResponseErrorInner,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct ResponseErrorInner {
49 pub code: Option<i64>,
50 pub message: String,
51 pub status: String,
52}
53
54#[derive(Error, Debug)]
55pub enum LoggerError {
56 #[error("ReqwestError: {0}")]
57 Reqwest(#[from] reqwest::Error),
58 #[error("Google error: {:?}", .0)]
59 Response(ResponseErrorInner),
60 #[error("Service Account: {}", .0)]
61 GAuth(#[from] GAuthError),
62}
63
64impl<M: LogMapper> GoogleLogger<M> {
65 pub fn new(
67 log_label: Arc<str>,
68 credential_bytes: impl AsRef<[u8]>,
69 mapper: M,
70 ) -> Result<GoogleLogger<M>, LoggerError> {
71 let credential_bytes = credential_bytes.as_ref();
72 let service_account = GAuth::from_bytes(credential_bytes, &SCOPES);
73 let project_id = GAuthCredential::from_bytes(credential_bytes)
74 .map_err(|e| LoggerError::GAuth(GAuthError::SerdeJson(e)))?
75 .project_id;
76
77 let project_id = Arc::from(project_id);
78
79 Ok(Self {
80 log_context: LogContext {
81 log_label,
82 project_id,
83 },
84 gauth: service_account,
85 http_client: Client::new(),
86 mapper,
87 })
88 }
89
90 pub async fn write_logs(&mut self, log_entry: Vec<Value>) -> Result<(), LoggerError> {
94 let access_token = self.gauth.access_token().await?;
95 let entries = log_entry
96 .into_iter()
97 .map(|v| self.mapper.map(self.context(), v))
98 .collect::<Vec<_>>();
99
100 let maybe_response_error = self
102 .http_client
103 .post(WRITE_URL)
104 .header("Content-Type", "application/json")
105 .header("Authorization", access_token)
106 .json(&json!({
107 "entries": entries,
108 }))
109 .send()
110 .await?
111 .json::<ResponseError>()
112 .await
113 .ok();
114
115 if let Some(ResponseError { error }) = maybe_response_error {
116 return Err(LoggerError::Response(error));
117 }
118
119 Ok(())
120 }
121
122 #[inline]
123 pub fn context(&self) -> LogContext {
124 self.log_context.clone()
125 }
126}