From 735b635190b4dab3546edc7c2b74713b247c3e12 Mon Sep 17 00:00:00 2001 From: Burak Date: Mon, 10 Jul 2023 10:31:01 +0100 Subject: [PATCH] Python: Allow configuring logging formatter (#2829) ## Motivation and Context Allow Python users to configure their logging formatter to either `json`, `pretty` or `compact` (default). ## Checklist - [ ] I have updated `CHANGELOG.next.toml` if I made changes to the smithy-rs codegen or runtime crates - [ ] I have updated `CHANGELOG.next.toml` if I made changes to the AWS SDK, generated SDK code, or SDK runtime crates ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._ --- examples/python/pokemon_service.py | 9 +- .../aws-smithy-http-server-python/Cargo.toml | 2 +- .../src/logging.rs | 83 ++++++++++++++----- .../src/tls/listener.rs | 2 +- 4 files changed, 72 insertions(+), 24 deletions(-) diff --git a/examples/python/pokemon_service.py b/examples/python/pokemon_service.py index eeb6445eb..552d892ff 100644 --- a/examples/python/pokemon_service.py +++ b/examples/python/pokemon_service.py @@ -50,7 +50,14 @@ from pokemon_service_server_sdk.types import ByteStream # Logging can bee setup using standard Python tooling. We provide # fast logging handler, Tracingandler based on Rust tracing crate. -logging.basicConfig(handlers=[TracingHandler(level=logging.DEBUG).handler()]) +logging.basicConfig( + handlers=[ + TracingHandler( + level=logging.DEBUG, + format="pretty", # You can also use "json" or "compact" (default) + ).handler() + ] +) class SafeCounter: diff --git a/rust-runtime/aws-smithy-http-server-python/Cargo.toml b/rust-runtime/aws-smithy-http-server-python/Cargo.toml index b454eab62..61ce57d7a 100644 --- a/rust-runtime/aws-smithy-http-server-python/Cargo.toml +++ b/rust-runtime/aws-smithy-http-server-python/Cargo.toml @@ -38,7 +38,7 @@ tokio = { version = "1.20.1", features = ["full"] } tokio-stream = "0.1" tower = { version = "0.4.13", features = ["util"] } tracing = "0.1.36" -tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } +tracing-subscriber = { version = "0.3.15", features = ["json", "env-filter"] } tracing-appender = { version = "0.2.2"} [dev-dependencies] diff --git a/rust-runtime/aws-smithy-http-server-python/src/logging.rs b/rust-runtime/aws-smithy-http-server-python/src/logging.rs index 80d4966ac..57fa2873a 100644 --- a/rust-runtime/aws-smithy-http-server-python/src/logging.rs +++ b/rust-runtime/aws-smithy-http-server-python/src/logging.rs @@ -4,7 +4,8 @@ */ //! Rust `tracing` and Python `logging` setup and utilities. -use std::path::PathBuf; + +use std::{path::PathBuf, str::FromStr}; use pyo3::prelude::*; #[cfg(not(test))] @@ -15,15 +16,49 @@ use tracing_subscriber::{ fmt::{self, writer::MakeWriterExt}, layer::SubscriberExt, util::SubscriberInitExt, + Layer, }; use crate::error::PyException; +#[derive(Debug, Default)] +enum Format { + Json, + Pretty, + #[default] + Compact, +} + +#[derive(Debug, PartialEq, Eq)] +struct InvalidFormatError; + +impl FromStr for Format { + type Err = InvalidFormatError; + + fn from_str(s: &str) -> Result { + match s { + "pretty" => Ok(Self::Pretty), + "json" => Ok(Self::Json), + "compact" => Ok(Self::Compact), + _ => Err(InvalidFormatError), + } + } +} + /// Setup tracing-subscriber to log on console or to a hourly rolling file. fn setup_tracing_subscriber( level: Option, logfile: Option, + format: Option, ) -> PyResult> { + let format = match format { + Some(format) => Format::from_str(&format).unwrap_or_else(|_| { + tracing::error!("unknown format '{format}', falling back to default formatter"); + Format::default() + }), + None => Format::default(), + }; + let appender = match logfile { Some(logfile) => { let parent = logfile.parent().ok_or_else(|| { @@ -54,27 +89,27 @@ fn setup_tracing_subscriber( _ => Level::TRACE, }; + let formatter = fmt::Layer::new().with_line_number(true).with_level(true); + match appender { Some((appender, guard)) => { - let layer = Some( - fmt::Layer::new() - .with_writer(appender.with_max_level(tracing_level)) - .with_ansi(true) - .with_line_number(true) - .with_level(true), - ); - tracing_subscriber::registry().with(layer).init(); + let formatter = formatter.with_writer(appender.with_max_level(tracing_level)); + let formatter = match format { + Format::Json => formatter.json().boxed(), + Format::Compact => formatter.compact().boxed(), + Format::Pretty => formatter.pretty().boxed(), + }; + tracing_subscriber::registry().with(formatter).init(); Ok(Some(guard)) } None => { - let layer = Some( - fmt::Layer::new() - .with_writer(std::io::stdout.with_max_level(tracing_level)) - .with_ansi(true) - .with_line_number(true) - .with_level(true), - ); - tracing_subscriber::registry().with(layer).init(); + let formatter = formatter.with_writer(std::io::stdout.with_max_level(tracing_level)); + let formatter = match format { + Format::Json => formatter.json().boxed(), + Format::Compact => formatter.compact().boxed(), + Format::Pretty => formatter.pretty().boxed(), + }; + tracing_subscriber::registry().with(formatter).init(); Ok(None) } } @@ -89,9 +124,10 @@ fn setup_tracing_subscriber( /// /// :param level typing.Optional\[int\]: /// :param logfile typing.Optional\[pathlib.Path\]: +/// :param format typing.Optional\[typing.Literal\['compact', 'pretty', 'json'\]\]: /// :rtype None: #[pyclass(name = "TracingHandler")] -#[pyo3(text_signature = "($self, level=None, logfile=None)")] +#[pyo3(text_signature = "($self, level=None, logfile=None, format=None)")] #[derive(Debug)] pub struct PyTracingHandler { _guard: Option, @@ -100,8 +136,13 @@ pub struct PyTracingHandler { #[pymethods] impl PyTracingHandler { #[new] - fn newpy(py: Python, level: Option, logfile: Option) -> PyResult { - let _guard = setup_tracing_subscriber(level, logfile)?; + fn newpy( + py: Python, + level: Option, + logfile: Option, + format: Option, + ) -> PyResult { + let _guard = setup_tracing_subscriber(level, logfile, format)?; let logging = py.import("logging")?; let root = logging.getattr("root")?; root.setattr("level", level)?; @@ -190,7 +231,7 @@ mod tests { fn tracing_handler_is_injected_in_python() { crate::tests::initialize(); Python::with_gil(|py| { - let handler = PyTracingHandler::newpy(py, Some(10), None).unwrap(); + let handler = PyTracingHandler::newpy(py, Some(10), None, None).unwrap(); let kwargs = PyDict::new(py); kwargs .set_item("handlers", vec![handler.handler(py).unwrap()]) diff --git a/rust-runtime/aws-smithy-http-server-python/src/tls/listener.rs b/rust-runtime/aws-smithy-http-server-python/src/tls/listener.rs index fb3aa7ac9..9e6dbed36 100644 --- a/rust-runtime/aws-smithy-http-server-python/src/tls/listener.rs +++ b/rust-runtime/aws-smithy-http-server-python/src/tls/listener.rs @@ -144,7 +144,7 @@ mod tests { assert!(response .unwrap_err() .to_string() - .contains("invalid peer certificate: UnknownIssuer")); + .contains("invalid peer certificate")); } { -- GitLab