process_event/
main.rs

1#![doc(
2    html_logo_url = "https://raw.githubusercontent.com/getsentry/relay/master/artwork/relay-icon.png",
3    html_favicon_url = "https://raw.githubusercontent.com/getsentry/relay/master/artwork/relay-icon.png"
4)]
5
6use std::fs;
7use std::io::{self, Read};
8use std::path::PathBuf;
9
10use anyhow::{Context, Result, format_err};
11use clap::Parser;
12use relay_event_normalization::{
13    EventValidationConfig, NormalizationConfig, normalize_event, validate_event,
14};
15use relay_event_schema::processor::{ProcessingState, process_value};
16use relay_event_schema::protocol::Event;
17use relay_pii::{PiiConfig, PiiProcessor};
18use relay_protocol::Annotated;
19
20/// Processes a Sentry event payload.
21///
22/// This command takes a JSON event payload on stdin and write the processed event payload to
23/// stdout. Optionally, an additional PII config can be supplied.
24#[derive(Debug, Parser)]
25#[structopt(verbatim_doc_comment)]
26struct Cli {
27    /// Path to a PII processing config JSON file.
28    #[arg(short = 'c', long)]
29    pii_config: Option<PathBuf>,
30
31    /// Path to an event payload JSON file (defaults to stdin).
32    #[arg(short, long)]
33    event: Option<PathBuf>,
34
35    /// Apply full store normalization.
36    #[arg(long)]
37    store: bool,
38
39    /// Pretty print the output JSON.
40    #[arg(long, conflicts_with = "debug")]
41    pretty: bool,
42
43    /// Debug print the internal structure.
44    #[arg(long)]
45    debug: bool,
46}
47
48impl Cli {
49    fn load_pii_config(&self) -> Result<Option<PiiConfig>> {
50        let path = match self.pii_config {
51            Some(ref path) => path,
52            None => return Ok(None),
53        };
54
55        let json = fs::read_to_string(path).with_context(|| "failed to read PII config")?;
56        let config = serde_json::from_str(&json).with_context(|| "failed to parse PII config")?;
57        Ok(Some(config))
58    }
59
60    fn load_event(&self) -> Result<Annotated<Event>> {
61        let json = match self.event {
62            Some(ref path) => fs::read_to_string(path).with_context(|| "failed to read event")?,
63            None => {
64                let mut json = String::new();
65                io::stdin()
66                    .read_to_string(&mut json)
67                    .with_context(|| "failed to read event")?;
68                json
69            }
70        };
71
72        let event = Annotated::from_json(&json).with_context(|| "failed to parse event")?;
73        Ok(event)
74    }
75
76    pub fn run(self) -> Result<()> {
77        let mut event = self.load_event()?;
78
79        if let Some(pii_config) = self.load_pii_config()? {
80            let mut processor = PiiProcessor::new(pii_config.compiled());
81            process_value(&mut event, &mut processor, ProcessingState::root())
82                .map_err(|e| format_err!("{e}"))?;
83        }
84
85        if self.store {
86            validate_event(&mut event, &EventValidationConfig::default())
87                .map_err(|e| format_err!("{e}"))?;
88            normalize_event(&mut event, &NormalizationConfig::default());
89        }
90
91        if self.debug {
92            println!("{event:#?}");
93        } else if self.pretty {
94            println!("{}", event.to_json_pretty()?);
95        } else {
96            println!("{}", event.to_json()?);
97        }
98
99        Ok(())
100    }
101}
102
103fn print_error(error: &anyhow::Error) {
104    eprintln!("Error: {error}");
105
106    let mut cause = error.source();
107    while let Some(ref e) = cause {
108        eprintln!("  caused by: {e}");
109        cause = e.source();
110    }
111}
112
113fn main() {
114    let cli = Cli::parse();
115
116    match cli.run() {
117        Ok(()) => (),
118        Err(error) => {
119            print_error(&error);
120            std::process::exit(1);
121        }
122    }
123}