diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index 546159c9d13dd..e36e231418ed3 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -30,6 +30,7 @@ use rustc_span::hygiene::{ExpnKind, MacroKind};
 use std::borrow::Cow;
 use std::cmp::{max, min, Reverse};
 use std::error::Report;
+use std::fs::File;
 use std::io::prelude::*;
 use std::io::{self, IsTerminal};
 use std::iter;
@@ -538,7 +539,7 @@ impl Emitter for EmitterWriter {
             &primary_span,
             &children,
             suggestions,
-            self.track_diagnostics.then_some(&diag.emitted_at),
+            &diag.emitted_at,
         );
     }
 
@@ -637,6 +638,7 @@ pub struct EmitterWriter {
 
     macro_backtrace: bool,
     track_diagnostics: bool,
+    metrics: Option<File>,
     terminal_url: TerminalUrl,
 }
 
@@ -666,6 +668,7 @@ impl EmitterWriter {
             diagnostic_width: None,
             macro_backtrace: false,
             track_diagnostics: false,
+            metrics: None,
             terminal_url: TerminalUrl::No,
         }
     }
@@ -2079,8 +2082,9 @@ impl EmitterWriter {
         span: &MultiSpan,
         children: &[SubDiagnostic],
         suggestions: &[CodeSuggestion],
-        emitted_at: Option<&DiagnosticLocation>,
+        emitted_at_location: &DiagnosticLocation,
     ) {
+        let emitted_at = self.track_diagnostics.then_some(emitted_at_location);
         let max_line_num_len = if self.ui_testing {
             ANONYMIZED_LINE_NUM.len()
         } else {
@@ -2088,6 +2092,17 @@ impl EmitterWriter {
             num_decimal_digits(n)
         };
 
+        if let Some(ref mut file) = &mut self.metrics {
+            let _ = writeln!(
+                file,
+                "{:?},{:?},{},{}",
+                code,
+                emitted_at_location,
+                children.len(),
+                suggestions.len()
+            );
+        }
+
         match self.emit_messages_default_inner(
             span,
             messages,
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index e436591fdd99b..4457c218b1c38 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -514,7 +514,7 @@ fn default_track_diagnostic(d: &mut Diagnostic, f: &mut dyn FnMut(&mut Diagnosti
 pub static TRACK_DIAGNOSTICS: AtomicRef<fn(&mut Diagnostic, &mut dyn FnMut(&mut Diagnostic))> =
     AtomicRef::new(&(default_track_diagnostic as _));
 
-#[derive(Copy, Clone, Default)]
+#[derive(Clone, Default)]
 pub struct DiagCtxtFlags {
     /// If false, warning-level lints are suppressed.
     /// (rustc: see `--allow warnings` and `--cap-lints`)
@@ -535,6 +535,8 @@ pub struct DiagCtxtFlags {
     pub deduplicate_diagnostics: bool,
     /// Track where errors are created. Enabled with `-Ztrack-diagnostics`.
     pub track_diagnostics: bool,
+    /// Track whether error metrics are stored. Enabled with `-Zerror-metrics=path`.
+    pub metrics: Option<PathBuf>,
 }
 
 impl Drop for DiagCtxtInner {
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index e1640d7fca93d..9612c876208e4 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -1165,6 +1165,7 @@ impl UnstableOptions {
             macro_backtrace: self.macro_backtrace,
             deduplicate_diagnostics: self.deduplicate_diagnostics,
             track_diagnostics: self.track_diagnostics,
+            metrics: self.error_metrics.clone(),
         }
     }
 }
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index 8274fd05bc057..386e2f3e935bb 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -1621,6 +1621,8 @@ options! {
         "emit a section containing stack size metadata (default: no)"),
     emit_thin_lto: bool = (true, parse_bool, [TRACKED],
         "emit the bc module with thin LTO info (default: yes)"),
+    error_metrics: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
+        "stores metrics about the errors being emitted by rustc to disk"),
     export_executable_symbols: bool = (false, parse_bool, [TRACKED],
         "export symbols from executables, as if they were dynamic libraries"),
     extra_const_ub_checks: bool = (false, parse_bool, [TRACKED],
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index 9ee7625e5bfeb..6d084d4fcad64 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -41,6 +41,7 @@ use std::any::Any;
 use std::cell::{self, RefCell};
 use std::env;
 use std::fmt;
+use std::fs::File;
 use std::ops::{Div, Mul};
 use std::path::{Path, PathBuf};
 use std::str::FromStr;
@@ -1009,6 +1010,12 @@ fn default_emitter(
                 );
                 Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing))
             } else {
+                let metrics = sopts.unstable_opts.error_metrics.as_ref().and_then(|path| {
+                    let mut path = path.clone();
+                    std::fs::create_dir_all(&path).ok()?;
+                    path.push(format!("error_metrics_{}", std::process::id()));
+                    File::options().create(true).append(true).open(&path).ok()
+                });
                 let emitter = EmitterWriter::stderr(color_config, fallback_bundle)
                     .fluent_bundle(bundle)
                     .sm(Some(source_map))
@@ -1018,6 +1025,7 @@ fn default_emitter(
                     .macro_backtrace(macro_backtrace)
                     .track_diagnostics(track_diagnostics)
                     .terminal_url(terminal_url)
+                    .metrics(metrics)
                     .ignored_directories_in_source_blocks(
                         sopts.unstable_opts.ignore_directory_in_diagnostics_source_blocks.clone(),
                     );