Unverified Commit 735b6351 authored by Burak's avatar Burak Committed by GitHub
Browse files

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
<!--- If a checkbox below is not applicable, then please DELETE it
rather than leaving it unchecked -->
- [ ] 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._
parent ddba4608
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -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:
+1 −1
Original line number Diff line number Diff line
@@ -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]
+62 −21
Original line number Diff line number Diff line
@@ -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<Self, Self::Err> {
        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<u8>,
    logfile: Option<PathBuf>,
    format: Option<String>,
) -> PyResult<Option<WorkerGuard>> {
    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<WorkerGuard>,
@@ -100,8 +136,13 @@ pub struct PyTracingHandler {
#[pymethods]
impl PyTracingHandler {
    #[new]
    fn newpy(py: Python, level: Option<u8>, logfile: Option<PathBuf>) -> PyResult<Self> {
        let _guard = setup_tracing_subscriber(level, logfile)?;
    fn newpy(
        py: Python,
        level: Option<u8>,
        logfile: Option<PathBuf>,
        format: Option<String>,
    ) -> PyResult<Self> {
        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()])
+1 −1
Original line number Diff line number Diff line
@@ -144,7 +144,7 @@ mod tests {
            assert!(response
                .unwrap_err()
                .to_string()
                .contains("invalid peer certificate: UnknownIssuer"));
                .contains("invalid peer certificate"));
        }

        {