diff --git a/Cargo.lock b/Cargo.lock
index 287cb0ba1f8..7173b7b8f51 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -394,7 +394,7 @@ name = "crossbeam-channel"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "crossbeam-epoch 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-epoch 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -404,18 +404,18 @@ name = "crossbeam-deque"
 version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "crossbeam-epoch 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-epoch 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "crossbeam-epoch"
-version = "0.4.2"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "crossbeam-utils 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -429,11 +429,6 @@ dependencies = [
  "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
-[[package]]
-name = "crossbeam-utils"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
 [[package]]
 name = "ctrlc"
 version = "3.1.1"
@@ -1519,6 +1514,7 @@ dependencies = [
 name = "semaphore"
 version = "0.1.1"
 dependencies = [
+ "chrono 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "console 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1536,6 +1532,9 @@ dependencies = [
  "semaphore-server 0.1.1",
  "semaphore-trove 0.1.1",
  "sentry 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -2524,9 +2523,8 @@ source = "git+https://github.com/getsentry/sentry-rust?rev=0bf9723d4bbb1820033d4
 "checksum crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be"
 "checksum crossbeam-channel 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "862becd07e73da5746de6d9b3ba055c9bb8b10afd0d2b51155a6e30d81cd20b3"
 "checksum crossbeam-deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fe8153ef04a7594ded05b427ffad46ddeaf22e63fd48d42b3e1e3bb4db07cae7"
-"checksum crossbeam-epoch 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "81fb27ecd6f6e30f19c34b23c368450bed3baa4658c8ad40a192381f2d3c9f1b"
+"checksum crossbeam-epoch 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2af0e75710d6181e234c8ecc79f14a97907850a541b13b0be1dd10992f2e4620"
 "checksum crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d636a8b3bcc1b409d7ffd3facef8f21dcb4009626adbd0c5e6c4305c07253c7b"
-"checksum crossbeam-utils 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b71f220442ed14749909b543d4dd7ec3918cb1fe289fd96e88d0abe6ca049783"
 "checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e"
 "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850"
 "checksum debugid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57c49686e3eebf4e7cf157f690fca61284e864fb5ce717634af4528f171048e0"
diff --git a/Cargo.toml b/Cargo.toml
index 21da677add5..a1c5f8cdac1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@ with_ssl = ["semaphore-server/with_ssl"]
 
 [profile.release]
 lto = true
+debug = true
 
 [dependencies]
 clap = { version = "2.31.2", default-features = false, features = ["wrap_help"] }
@@ -24,12 +25,16 @@ tokio-core = "0.1.17"
 futures = "0.1.21"
 parking_lot = "0.5.5"
 ctrlc = { version = "3.1.1", features = ["termination"] }
-log = "0.4.2"
+log = { version = "0.4.2", features = ["serde"] }
 sentry = "0.5.4"
 dialoguer = "0.1.0"
 uuid = "0.6.5"
 console = "0.6.1"
 env_logger = "0.5.10"
+chrono = "0.4.3"
+serde_derive = "1.0.66"
+serde = "1.0.66"
+serde_json = "1.0.20"
 
 [dependencies.semaphore-aorta]
 path = "aorta"
diff --git a/config/src/types.rs b/config/src/types.rs
index 645b595922f..615b53aeadf 100644
--- a/config/src/types.rs
+++ b/config/src/types.rs
@@ -124,6 +124,20 @@ pub struct MinimalConfig {
     pub relay: Relay,
 }
 
+/// Controls the log format
+#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
+#[serde(rename_all = "lowercase")]
+pub enum LogFormat {
+    /// Auto detect (pretty for tty, simplified for other)
+    Auto,
+    /// With colors
+    Pretty,
+    /// Simplified log output
+    Simplified,
+    /// Dump out JSON lines
+    Json,
+}
+
 /// Controls the logging system.
 #[derive(Serialize, Deserialize, Debug)]
 #[serde(default)]
@@ -132,6 +146,8 @@ struct Logging {
     level: log::LevelFilter,
     /// If set to true this emits log messages for failed event payloads.
     log_failed_payloads: bool,
+    /// Controls the log format.
+    format: LogFormat,
     /// When set to true, backtraces are forced on.
     enable_backtraces: bool,
 }
@@ -194,6 +210,7 @@ impl Default for Logging {
         Logging {
             level: log::LevelFilter::Info,
             log_failed_payloads: false,
+            format: LogFormat::Auto,
             enable_backtraces: true,
         }
     }
@@ -514,6 +531,11 @@ impl Config {
         self.values.logging.log_failed_payloads
     }
 
+    /// Which log format should be used?
+    pub fn log_format(&self) -> LogFormat {
+        self.values.logging.format
+    }
+
     /// Returns the socket addresses for statsd.
     ///
     /// If stats is disabled an empty vector is returned.
diff --git a/server/src/extractors.rs b/server/src/extractors.rs
index 61f899fd0d4..2560dbd7666 100644
--- a/server/src/extractors.rs
+++ b/server/src/extractors.rs
@@ -116,16 +116,25 @@ impl<T: FromRequest<ServiceState> + 'static> FromRequest<ServiceState> for Proje
 
         req.extensions_mut().insert(auth.clone());
 
-        match req.match_info()
+        let project_id = match req.match_info()
             .get("project")
             .unwrap_or_default()
             .parse::<ProjectId>()
             .map_err(BadProjectRequest::BadProject)
         {
-            Ok(project_id) => req.extensions_mut().insert(project_id),
+            Ok(project_id) => project_id,
             Err(err) => return Box::new(future::err(err.into())),
         };
 
+        sentry::configure_scope(|scope| {
+            scope.set_user(Some(sentry::User {
+                id: Some(project_id.to_string()),
+                ..Default::default()
+            }));
+        });
+
+        req.extensions_mut().insert(project_id);
+
         Box::new(
             T::from_request(&req, cfg)
                 .into()
diff --git a/src/main.rs b/src/main.rs
index e1b39628fa8..341199f9eb0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,6 +13,11 @@ extern crate parking_lot;
 extern crate pretty_env_logger;
 #[macro_use]
 extern crate sentry;
+extern crate serde;
+#[macro_use]
+extern crate serde_derive;
+extern crate chrono;
+extern crate serde_json;
 extern crate uuid;
 
 extern crate semaphore_common;
diff --git a/src/setup.rs b/src/setup.rs
index b76cdd70e93..cd51928856d 100644
--- a/src/setup.rs
+++ b/src/setup.rs
@@ -1,15 +1,18 @@
 use std::env;
+use std::io;
 use std::io::Write;
 
+use chrono::{DateTime, Utc};
 use console;
 use env_logger;
 use failure::Error;
-use log::LevelFilter;
+use log::{Level, LevelFilter};
 use pretty_env_logger;
 use sentry;
+use serde_json;
 
 use semaphore_common::metrics;
-use semaphore_config::Config;
+use semaphore_config::{Config, LogFormat};
 
 /// Print spawn infos to the log.
 pub fn dump_spawn_infos(config: &Config) {
@@ -79,22 +82,56 @@ pub fn init_logging(config: &Config) {
     }
 
     let mut log_builder = {
-        if console::user_attended() {
-            pretty_env_logger::formatted_builder().unwrap()
-        } else {
-            let mut builder = env_logger::Builder::new();
-            builder.format(|buf, record| {
-                let ts = buf.timestamp();
-                writeln!(
-                    buf,
-                    "{} [{}] {}: {}",
-                    ts,
-                    record.module_path().unwrap_or("<unknown>"),
-                    record.level(),
-                    record.args()
-                )
-            });
-            builder
+        match (config.log_format(), console::user_attended()) {
+            (LogFormat::Auto, true) | (LogFormat::Pretty, _) => {
+                pretty_env_logger::formatted_builder().unwrap()
+            }
+            (LogFormat::Auto, false) | (LogFormat::Simplified, _) => {
+                let mut builder = env_logger::Builder::new();
+                builder.format(|buf, record| {
+                    let ts = buf.timestamp();
+                    writeln!(
+                        buf,
+                        "{} [{}] {}: {}",
+                        ts,
+                        record.module_path().unwrap_or("<unknown>"),
+                        record.level(),
+                        record.args()
+                    )
+                });
+                builder
+            }
+            (LogFormat::Json, _) => {
+                #[derive(Serialize, Deserialize, Debug)]
+                struct LogRecord<'a> {
+                    timestamp: DateTime<Utc>,
+                    level: Level,
+                    logger: &'a str,
+                    message: String,
+                    module_path: Option<&'a str>,
+                    filename: Option<&'a str>,
+                    lineno: Option<u32>,
+                }
+
+                let mut builder = env_logger::Builder::new();
+                builder.format(|mut buf, record| -> io::Result<()> {
+                    serde_json::to_writer(
+                        &mut buf,
+                        &LogRecord {
+                            timestamp: Utc::now(),
+                            level: record.level(),
+                            logger: record.target(),
+                            message: record.args().to_string(),
+                            module_path: record.module_path(),
+                            filename: record.file(),
+                            lineno: record.line(),
+                        },
+                    ).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
+                    buf.write(b"\n")?;
+                    Ok(())
+                });
+                builder
+            }
         }
     };