servo: Merge #11841 - Send log messages to the constellation (from asajeffrey:constellation-logging); r=Manishearth
authorAlan Jeffrey <ajeffrey@mozilla.com>
Fri, 15 Jul 2016 12:22:26 -0700
changeset 339303 d90b7d6759ec7465e6cf29a5e65c9b4ab954d973
parent 339302 e66960190602fa8d7dae526888bf7a6dc79012b0
child 339304 a26977fad24652d68810b6936efa2af6a1fc1574
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersManishearth
servo: Merge #11841 - Send log messages to the constellation (from asajeffrey:constellation-logging); r=Manishearth <!-- Please describe your changes on the following line: --> Send all warnings and errors to the constellation. Warnings are bufferred up, and included in any subsequent error reports. Errors are reported in the same way as panics. Note that this can't merge yet, as it needs https://github.com/rust-lang-nursery/log/pull/86 to land. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #11776 (github issue number if applicable). - [X] These changes do not require tests because we don't test crash reporting. <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: a5cd4b95183da4ae9b754d9d4824bd23f4ad7eb8
servo/components/constellation/Cargo.toml
servo/components/constellation/constellation.rs
servo/components/constellation/lib.rs
servo/components/constellation/pipeline.rs
servo/components/script_traits/lib.rs
servo/components/script_traits/script_msg.rs
servo/components/servo/Cargo.lock
servo/components/servo/lib.rs
servo/components/servo/main.rs
servo/ports/cef/Cargo.lock
servo/ports/geckolib/Cargo.lock
--- a/servo/components/constellation/Cargo.toml
+++ b/servo/components/constellation/Cargo.toml
@@ -5,16 +5,17 @@ authors = ["The Servo Project Developers
 license = "MPL-2.0"
 publish = false
 
 [lib]
 name = "constellation"
 path = "lib.rs"
 
 [dependencies]
+backtrace = "0.2.1"
 canvas = {path = "../canvas"}
 canvas_traits = {path = "../canvas_traits"}
 clipboard = {git = "https://github.com/aweinstock314/rust-clipboard"}
 compositing = {path = "../compositing"}
 devtools_traits = {path = "../devtools_traits"}
 euclid = "0.7.1"
 gfx = {path = "../gfx"}
 gfx_traits = {path = "../gfx_traits"}
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -4,31 +4,33 @@
 
 //! The `Constellation`, Servo's Grand Central Station
 //!
 //! The primary duty of a `Constellation` is to mediate between the
 //! graphics compositor and the many `Pipeline`s in the browser's
 //! navigation context, each `Pipeline` encompassing a `ScriptThread`,
 //! `LayoutThread`, and `PaintThread`.
 
+use backtrace::Backtrace;
 use canvas::canvas_paint_thread::CanvasPaintThread;
 use canvas::webgl_paint_thread::WebGLPaintThread;
 use canvas_traits::CanvasMsg;
 use clipboard::ClipboardContext;
 use compositing::SendableFrameTree;
 use compositing::compositor_thread::CompositorProxy;
 use compositing::compositor_thread::Msg as ToCompositorMsg;
 use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg};
 use euclid::scale_factor::ScaleFactor;
 use euclid::size::{Size2D, TypedSize2D};
 use gfx::font_cache_thread::FontCacheThread;
 use gfx_traits::Epoch;
 use ipc_channel::ipc::{self, IpcSender};
 use ipc_channel::router::ROUTER;
 use layout_traits::LayoutThreadFactory;
+use log::{Log, LogLevel, LogLevelFilter, LogMetadata, LogRecord};
 use msg::constellation_msg::{FrameId, FrameType, PipelineId};
 use msg::constellation_msg::{Key, KeyModifiers, KeyState, LoadData};
 use msg::constellation_msg::{PipelineNamespace, PipelineNamespaceId, NavigationDirection};
 use msg::constellation_msg::{SubpageId, WindowSizeType};
 use msg::constellation_msg::{self, PanicMsg};
 use net_traits::bluetooth_thread::BluetoothMethodMsg;
 use net_traits::filemanager_thread::FileManagerThreadMsg;
 use net_traits::image_cache_thread::ImageCacheThread;
@@ -40,24 +42,26 @@ use profile_traits::mem;
 use profile_traits::time;
 use rand::{random, Rng, SeedableRng, StdRng};
 use script_traits::webdriver_msg;
 use script_traits::{AnimationState, AnimationTickType, CompositorEvent};
 use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorMsg};
 use script_traits::{DocumentState, LayoutControlMsg};
 use script_traits::{IFrameLoadInfo, IFrameSandboxState, TimerEventRequest};
 use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory};
-use script_traits::{MozBrowserEvent, MozBrowserErrorType, WebDriverCommandMsg, WindowSizeData};
+use script_traits::{LogEntry, MozBrowserEvent, MozBrowserErrorType, WebDriverCommandMsg, WindowSizeData};
 use std::borrow::ToOwned;
-use std::collections::HashMap;
+use std::collections::{HashMap, VecDeque};
 use std::io::Error as IOError;
 use std::marker::PhantomData;
 use std::mem::replace;
 use std::process;
 use std::sync::mpsc::{Sender, channel, Receiver};
+use std::sync::{Arc, Mutex};
+use std::thread;
 use style_traits::PagePx;
 use style_traits::cursor::Cursor;
 use style_traits::viewport::ViewportConstraints;
 use timer_scheduler::TimerScheduler;
 use url::Url;
 use util::opts;
 use util::prefs::PREFS;
 use util::thread::spawn_named;
@@ -177,16 +181,20 @@ pub struct Constellation<Message, LTF, S
     webrender_api_sender: Option<webrender_traits::RenderApiSender>,
 
     /// Are we shutting down?
     shutting_down: bool,
 
     /// Have we seen any panics? Hopefully always false!
     handled_panic: bool,
 
+    /// Have we seen any warnings? Hopefully always empty!
+    /// The buffer contains `(thread_name, reason)` entries.
+    handled_warnings: VecDeque<(Option<String>, String)>,
+
     /// The random number generator and probability for closing pipelines.
     /// This is for testing the hardening of the constellation.
     random_pipeline_closure: Option<(StdRng, f32)>,
 }
 
 /// State needed to construct a constellation.
 pub struct InitialConstellationState {
     /// A channel through which messages can be sent to the compositor.
@@ -299,16 +307,110 @@ impl WebDriverData {
 }
 
 #[derive(Clone, Copy)]
 enum ExitPipelineMode {
     Normal,
     Force,
 }
 
+/// A logger directed at the constellation from content processes
+#[derive(Clone)]
+pub struct FromScriptLogger {
+    /// A channel to the constellation
+    pub constellation_chan: Arc<Mutex<IpcSender<FromScriptMsg>>>,
+}
+
+impl FromScriptLogger {
+    /// Create a new constellation logger.
+    pub fn new(constellation_chan: IpcSender<FromScriptMsg>) -> FromScriptLogger {
+        FromScriptLogger {
+            constellation_chan: Arc::new(Mutex::new(constellation_chan))
+        }
+    }
+
+    /// The maximum log level the constellation logger is interested in.
+    pub fn filter(&self) -> LogLevelFilter {
+        LogLevelFilter::Warn
+    }
+}
+
+impl Log for FromScriptLogger {
+    fn enabled(&self, metadata: &LogMetadata) -> bool {
+        metadata.level() <= LogLevel::Warn
+    }
+
+    fn log(&self, record: &LogRecord) {
+        if let Some(entry) = log_entry(record) {
+            // TODO: Store the pipeline id in TLS so we can recover it here
+            let thread_name = thread::current().name().map(ToOwned::to_owned);
+            let msg = FromScriptMsg::LogEntry(None, thread_name, entry);
+            if let Ok(chan) = self.constellation_chan.lock() {
+                let _ = chan.send(msg);
+            }
+        }
+    }
+}
+
+/// A logger directed at the constellation from the compositor
+#[derive(Clone)]
+pub struct FromCompositorLogger {
+    /// A channel to the constellation
+    pub constellation_chan: Arc<Mutex<Sender<FromCompositorMsg>>>,
+}
+
+impl FromCompositorLogger {
+    /// Create a new constellation logger.
+    pub fn new(constellation_chan: Sender<FromCompositorMsg>) -> FromCompositorLogger {
+        FromCompositorLogger {
+            constellation_chan: Arc::new(Mutex::new(constellation_chan))
+        }
+    }
+
+    /// The maximum log level the constellation logger is interested in.
+    pub fn filter(&self) -> LogLevelFilter {
+        LogLevelFilter::Warn
+    }
+}
+
+impl Log for FromCompositorLogger {
+    fn enabled(&self, metadata: &LogMetadata) -> bool {
+        metadata.level() <= LogLevel::Warn
+    }
+
+    fn log(&self, record: &LogRecord) {
+        if let Some(entry) = log_entry(record) {
+            // TODO: Store the pipeline id in TLS so we can recover it here
+            let thread_name = thread::current().name().map(ToOwned::to_owned);
+            let msg = FromCompositorMsg::LogEntry(None, thread_name, entry);
+            if let Ok(chan) = self.constellation_chan.lock() {
+                let _ = chan.send(msg);
+            }
+        }
+    }
+}
+
+fn log_entry(record: &LogRecord) -> Option<LogEntry> {
+    match record.level() {
+        LogLevel::Error if thread::panicking() => Some(LogEntry::Panic(
+            format!("{}", record.args()),
+            format!("{:?}", Backtrace::new())
+        )),
+        LogLevel::Error => Some(LogEntry::Error(
+            format!("{}", record.args())
+        )),
+        LogLevel::Warn => Some(LogEntry::Warn(
+            format!("{}", record.args())
+        )),
+        _ => None,
+    }
+}
+
+const WARNINGS_BUFFER_SIZE: usize = 32;
+
 impl<Message, LTF, STF> Constellation<Message, LTF, STF>
     where LTF: LayoutThreadFactory<Message=Message>,
           STF: ScriptThreadFactory<Message=Message>
 {
     pub fn start(state: InitialConstellationState) -> Sender<FromCompositorMsg> {
         let (compositor_sender, compositor_receiver) = channel();
 
         spawn_named("Constellation".to_owned(), move || {
@@ -362,16 +464,17 @@ impl<Message, LTF, STF> Constellation<Me
                 },
                 webdriver: WebDriverData::new(),
                 scheduler_chan: TimerScheduler::start(),
                 child_processes: Vec::new(),
                 document_states: HashMap::new(),
                 webrender_api_sender: state.webrender_api_sender,
                 shutting_down: false,
                 handled_panic: false,
+                handled_warnings: VecDeque::new(),
                 random_pipeline_closure: opts::get().random_pipeline_closure_probability.map(|prob| {
                     let seed = opts::get().random_pipeline_closure_seed.unwrap_or_else(random);
                     let rng = StdRng::from_seed(&[seed]);
                     warn!("Randomly closing pipelines.");
                     info!("Using seed {} for random pipeline closure.", seed);
                     (rng, prob)
                 }),
             };
@@ -616,16 +719,19 @@ impl<Message, LTF, STF> Constellation<Me
             FromCompositorMsg::WebDriverCommand(command) => {
                 debug!("constellation got webdriver command message");
                 self.handle_webdriver_msg(command);
             }
             FromCompositorMsg::Reload => {
                 debug!("constellation got reload message");
                 self.handle_reload_msg();
             }
+            FromCompositorMsg::LogEntry(pipeline_id, thread_name, entry) => {
+                self.handle_log_entry(pipeline_id, thread_name, entry);
+            }
         }
     }
 
     fn handle_request_from_script(&mut self, message: FromScriptMsg) {
         match message {
             FromScriptMsg::PipelineExited(pipeline_id) => {
                 self.handle_pipeline_exited(pipeline_id);
             }
@@ -793,16 +899,19 @@ impl<Message, LTF, STF> Constellation<Me
 
             FromScriptMsg::ResizeTo(size) => {
                 self.compositor_proxy.send(ToCompositorMsg::ResizeTo(size));
             }
 
             FromScriptMsg::Exit => {
                 self.compositor_proxy.send(ToCompositorMsg::Exit);
             }
+            FromScriptMsg::LogEntry(pipeline_id, thread_name, entry) => {
+                self.handle_log_entry(pipeline_id, thread_name, entry);
+            }
 
             FromScriptMsg::SetTitle(pipeline_id, title) => {
                 self.compositor_proxy.send(ToCompositorMsg::ChangePageTitle(pipeline_id, title))
             }
 
             FromScriptMsg::SendKeyEvent(ch, key, key_state, key_modifiers) => {
                 self.compositor_proxy.send(ToCompositorMsg::KeyEvent(ch, key, key_state, key_modifiers))
             }
@@ -968,16 +1077,31 @@ impl<Message, LTF, STF> Constellation<Me
 
             self.push_pending_frame(new_pipeline_id, Some(pipeline_id));
 
         }
 
         self.handled_panic = true;
     }
 
+    // TODO: trigger a mozbrowsererror even if there's no pipeline id
+    fn handle_log_entry(&mut self, pipeline_id: Option<PipelineId>, thread_name: Option<String>, entry: LogEntry) {
+        match (pipeline_id, entry) {
+            (Some(pipeline_id), LogEntry::Panic(reason, backtrace)) =>
+                self.trigger_mozbrowsererror(pipeline_id, reason, backtrace),
+            (None, LogEntry::Panic(reason, _)) | (_, LogEntry::Error(reason)) | (_, LogEntry::Warn(reason)) => {
+                // VecDeque::truncate is unstable
+                if WARNINGS_BUFFER_SIZE <= self.handled_warnings.len() {
+                    self.handled_warnings.pop_front();
+                }
+                self.handled_warnings.push_back((thread_name, reason));
+            },
+        }
+    }
+
     fn handle_init_load(&mut self, url: Url) {
         let window_size = self.window_size.visible_viewport;
         let root_pipeline_id = PipelineId::new();
         debug_assert!(PipelineId::fake_root_pipeline_id() == root_pipeline_id);
         self.new_pipeline(root_pipeline_id, None, Some(window_size), None,
                           LoadData::new(url.clone(), None, None), false);
         self.handle_load_start_msg(&root_pipeline_id);
         self.push_pending_frame(root_pipeline_id, None);
@@ -2045,17 +2169,17 @@ impl<Message, LTF, STF> Constellation<Me
             if let Some(pipeline_id) = rng.choose(&*pipeline_ids) {
                 if let Some(pipeline) = self.pipelines.get(pipeline_id) {
                     // Don't kill the mozbrowser pipeline
                     if PREFS.is_mozbrowser_enabled() && pipeline.parent_info.is_none() {
                         info!("Not closing mozbrowser pipeline {}.", pipeline_id);
                     } else {
                         // Note that we deliberately do not do any of the tidying up
                         // associated with closing a pipeline. The constellation should cope!
-                        info!("Randomly closing pipeline {}.", pipeline_id);
+                        warn!("Randomly closing pipeline {}.", pipeline_id);
                         pipeline.force_exit();
                     }
                 }
             }
         }
     }
 
     // Convert a frame to a sendable form to pass to the compositor
@@ -2151,25 +2275,40 @@ impl<Message, LTF, STF> Constellation<Me
                     }
                 }
             }
         }
     }
 
     // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsererror
     // Note that this does not require the pipeline to be an immediate child of the root
-    fn trigger_mozbrowsererror(&self, pipeline_id: PipelineId, reason: String, backtrace: String) {
+    // TODO: allow the pipeline id to be optional, triggering the error on the root if it's not provided.
+    fn trigger_mozbrowsererror(&mut self, pipeline_id: PipelineId, reason: String, backtrace: String) {
         if !PREFS.is_mozbrowser_enabled() { return; }
 
         let ancestor_info = self.get_mozbrowser_ancestor_info(pipeline_id);
 
         if let Some(ancestor_info) = ancestor_info {
             match self.pipelines.get(&ancestor_info.0) {
                 Some(ancestor) => {
-                    let event = MozBrowserEvent::Error(MozBrowserErrorType::Fatal, Some(reason), Some(backtrace));
+                    let mut report = String::new();
+                    for (thread_name, warning) in self.handled_warnings.drain(..) {
+                        report.push_str("\nWARNING: ");
+                        if let Some(thread_name) = thread_name {
+                            report.push_str("<");
+                            report.push_str(&*thread_name);
+                            report.push_str(">: ");
+                        }
+                        report.push_str(&*warning);
+                    }
+                    report.push_str("\nERROR: ");
+                    report.push_str(&*reason);
+                    report.push_str("\n\n");
+                    report.push_str(&*backtrace);
+                    let event = MozBrowserEvent::Error(MozBrowserErrorType::Fatal, Some(reason), Some(report));
                     ancestor.trigger_mozbrowser_event(ancestor_info.1, event);
                 },
                 None => return warn!("Mozbrowsererror via closed pipeline {:?}.", ancestor_info.0),
             }
         }
     }
 
     fn focused_pipeline_in_tree(&self, frame_id: FrameId) -> bool {
--- a/servo/components/constellation/lib.rs
+++ b/servo/components/constellation/lib.rs
@@ -7,16 +7,17 @@
 #![feature(plugin)]
 #![feature(mpsc_select)]
 #![feature(plugin)]
 #![plugin(plugins)]
 
 #![deny(unsafe_code)]
 #![plugin(serde_macros)]
 
+extern crate backtrace;
 extern crate canvas;
 extern crate canvas_traits;
 extern crate clipboard;
 extern crate compositing;
 extern crate devtools_traits;
 extern crate euclid;
 #[cfg(not(target_os = "windows"))]
 extern crate gaol;
@@ -42,12 +43,12 @@ extern crate util;
 extern crate webrender_traits;
 
 mod constellation;
 mod pipeline;
 #[cfg(not(target_os = "windows"))]
 mod sandboxing;
 mod timer_scheduler;
 
-pub use constellation::{Constellation, InitialConstellationState};
+pub use constellation::{Constellation, FromCompositorLogger, FromScriptLogger, InitialConstellationState};
 pub use pipeline::UnprivilegedPipelineContent;
 #[cfg(not(target_os = "windows"))]
 pub use sandboxing::content_process_sandbox_profile;
--- a/servo/components/constellation/pipeline.rs
+++ b/servo/components/constellation/pipeline.rs
@@ -530,16 +530,20 @@ impl UnprivilegedPipelineContent {
     }
 
     #[cfg(target_os = "windows")]
     pub fn spawn_multiprocess(self) -> Result<ChildProcess, IOError> {
         error!("Multiprocess is not supported on Windows.");
         process::exit(1);
     }
 
+    pub fn constellation_chan(&self) -> IpcSender<ScriptMsg> {
+        self.constellation_chan.clone()
+    }
+
     pub fn opts(&self) -> Opts {
         self.opts.clone()
     }
 
     pub fn prefs(&self) -> HashMap<String, Pref> {
         self.prefs.clone()
     }
 }
--- a/servo/components/script_traits/lib.rs
+++ b/servo/components/script_traits/lib.rs
@@ -61,17 +61,17 @@ use profile_traits::mem;
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
 use std::collections::HashMap;
 use std::sync::mpsc::{Sender, Receiver};
 use style_traits::{PagePx, ViewportPx};
 use url::Url;
 use util::ipc::OptionalOpaqueIpcSender;
 use webdriver_msg::{LoadStatus, WebDriverScriptCommand};
 
-pub use script_msg::{LayoutMsg, ScriptMsg, EventResult};
+pub use script_msg::{LayoutMsg, ScriptMsg, EventResult, LogEntry};
 
 /// The address of a node. Layout sends these back. They must be validated via
 /// `from_untrusted_node_address` before they can be used, because we do not trust layout.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub struct UntrustedNodeAddress(pub *const c_void);
 
 impl HeapSizeOf for UntrustedNodeAddress {
     fn heap_size_of_children(&self) -> usize {
@@ -431,16 +431,17 @@ pub enum MozBrowserEvent {
     /// Sent when the scroll position within a browser `<iframe>` changes.
     AsyncScroll,
     /// Sent when window.close() is called within a browser `<iframe>`.
     Close,
     /// Sent when a browser `<iframe>` tries to open a context menu. This allows
     /// handling `<menuitem>` element available within the browser `<iframe>`'s content.
     ContextMenu,
     /// Sent when an error occurred while trying to load content within a browser `<iframe>`.
+    /// Includes an optional human-readable description, and an optional machine-readable report.
     Error(MozBrowserErrorType, Option<String>, Option<String>),
     /// Sent when the favicon of a browser `<iframe>` changes.
     IconChange(String, String, String),
     /// Sent when the browser `<iframe>` has reached the server.
     Connected,
     /// Sent when the browser `<iframe>` has finished loading all its assets.
     LoadEnd,
     /// Sent when the browser `<iframe>` starts to load a new page.
@@ -594,9 +595,11 @@ pub enum ConstellationMsg {
     /// Inform the constellation of a window being resized.
     WindowSize(WindowSizeData, WindowSizeType),
     /// Requests that the constellation instruct layout to begin a new tick of the animation.
     TickAnimation(PipelineId, AnimationTickType),
     /// Dispatch a webdriver command
     WebDriverCommand(WebDriverCommandMsg),
     /// Reload the current page.
     Reload,
+    /// A log entry, with the pipeline id and thread name
+    LogEntry(Option<PipelineId>, Option<String>, LogEntry),
 }
--- a/servo/components/script_traits/script_msg.rs
+++ b/servo/components/script_traits/script_msg.rs
@@ -35,16 +35,29 @@ pub enum LayoutMsg {
 #[derive(Deserialize, Serialize)]
 pub enum EventResult {
     /// Allowed by web content
     DefaultAllowed,
     /// Prevented by web content
     DefaultPrevented,
 }
 
+/// A log entry reported to the constellation
+/// We don't report all log entries, just serious ones.
+/// We need a separate type for this because LogLevel isn't serializable.
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub enum LogEntry {
+    /// Panic, with a reason and backtrace
+    Panic(String, String),
+    /// Error, with a reason
+    Error(String),
+    /// warning, with a reason
+    Warn(String)
+}
+
 /// Messages from the script to the constellation.
 #[derive(Deserialize, Serialize)]
 pub enum ScriptMsg {
     /// Indicates whether this pipeline is currently running animations.
     ChangeRunningAnimationsState(PipelineId, AnimationState),
     /// Requests that a new 2D canvas thread be created. (This is done in the constellation because
     /// 2D canvases may use the GPU and we don't want to give untrusted content access to the GPU.)
     CreateCanvasPaintThread(Size2D<i32>, IpcSender<IpcSender<CanvasMsg>>),
@@ -109,13 +122,15 @@ pub enum ScriptMsg {
     /// Move the window to a point
     MoveTo(Point2D<i32>),
     /// Resize the window to size
     ResizeTo(Size2D<u32>),
     /// Script has handled a touch event, and either prevented or allowed default actions.
     TouchEventProcessed(EventResult),
     /// Get Scroll Offset
     GetScrollOffset(PipelineId, LayerId, IpcSender<Point2D<f32>>),
+    /// A log entry, with the pipeline id and thread name
+    LogEntry(Option<PipelineId>, Option<String>, LogEntry),
     /// Notifies the constellation that this pipeline has exited.
     PipelineExited(PipelineId),
     /// Requests that the compositor shut down.
     Exit,
 }
--- a/servo/components/servo/Cargo.lock
+++ b/servo/components/servo/Cargo.lock
@@ -7,17 +7,17 @@ dependencies = [
  "browserhtml 0.1.15 (git+https://github.com/browserhtml/browserhtml?branch=gh-pages)",
  "canvas 0.0.1",
  "canvas_traits 0.0.1",
  "compiletest_helper 0.0.1",
  "compositing 0.0.1",
  "constellation 0.0.1",
  "devtools 0.0.1",
  "devtools_traits 0.0.1",
- "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "env_logger 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gaol 0.0.1 (git+https://github.com/servo/gaol)",
  "gfx 0.0.1",
  "gfx_tests 0.0.1",
  "gleam 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "glutin_app 0.0.1",
  "ipc-channel 0.4.0 (git+https://github.com/servo/ipc-channel)",
  "layout 0.0.1",
@@ -352,16 +352,17 @@ dependencies = [
  "webrender 0.1.0 (git+https://github.com/servo/webrender)",
  "webrender_traits 0.2.0 (git+https://github.com/servo/webrender_traits)",
 ]
 
 [[package]]
 name = "constellation"
 version = "0.0.1"
 dependencies = [
+ "backtrace 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "canvas 0.0.1",
  "canvas_traits 0.0.1",
  "clipboard 0.1.2 (git+https://github.com/aweinstock314/rust-clipboard)",
  "compositing 0.0.1",
  "devtools_traits 0.0.1",
  "euclid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gaol 0.0.1 (git+https://github.com/servo/gaol)",
  "gfx 0.0.1",
@@ -665,17 +666,17 @@ name = "enum_primitive"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "num 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "env_logger"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "euclid"
--- a/servo/components/servo/lib.rs
+++ b/servo/components/servo/lib.rs
@@ -12,20 +12,22 @@
 //! The `Browser` type is responsible for configuring a
 //! `Constellation`, which does the heavy lifting of coordinating all
 //! of Servo's internal subsystems, including the `ScriptThread` and the
 //! `LayoutThread`, as well maintains the navigation context.
 //!
 //! The `Browser` is fed events from a generic type that implements the
 //! `WindowMethods` trait.
 
+extern crate env_logger;
 #[cfg(not(target_os = "windows"))]
 extern crate gaol;
 #[macro_use]
 extern crate gleam;
+extern crate log;
 
 pub extern crate canvas;
 pub extern crate canvas_traits;
 pub extern crate compositing;
 pub extern crate constellation;
 pub extern crate devtools;
 pub extern crate devtools_traits;
 pub extern crate euclid;
@@ -60,30 +62,34 @@ fn webdriver(_port: u16, _constellation:
 
 use compositing::compositor_thread::InitialCompositorState;
 use compositing::windowing::WindowEvent;
 use compositing::windowing::WindowMethods;
 use compositing::{CompositorProxy, IOCompositor};
 #[cfg(not(target_os = "windows"))]
 use constellation::content_process_sandbox_profile;
 use constellation::{Constellation, InitialConstellationState, UnprivilegedPipelineContent};
+use constellation::{FromScriptLogger, FromCompositorLogger};
+use env_logger::Logger as EnvLogger;
 #[cfg(not(target_os = "windows"))]
 use gaol::sandbox::{ChildSandbox, ChildSandboxMethods};
 use gfx::font_cache_thread::FontCacheThread;
 use ipc_channel::ipc::{self, IpcSender};
+use log::{Log, LogMetadata, LogRecord};
 use net::bluetooth_thread::BluetoothThreadFactory;
 use net::image_cache_thread::new_image_cache_thread;
 use net::resource_thread::new_resource_threads;
 use net_traits::IpcSend;
 use net_traits::bluetooth_thread::BluetoothMethodMsg;
 use profile::mem as profile_mem;
 use profile::time as profile_time;
 use profile_traits::mem;
 use profile_traits::time;
-use script_traits::ConstellationMsg;
+use script_traits::{ConstellationMsg, ScriptMsg};
+use std::cmp::max;
 use std::rc::Rc;
 use std::sync::mpsc::Sender;
 use util::opts;
 use util::prefs::PREFS;
 use util::resource_files::resources_dir_path;
 
 pub use gleam::gl;
 
@@ -95,16 +101,17 @@ pub use gleam::gl;
 ///
 /// Clients create a `Browser` for a given reference-counted type
 /// implementing `WindowMethods`, which is the bridge to whatever
 /// application Servo is embedded in. Clients then create an event
 /// loop to pump messages between the embedding application and
 /// various browser components.
 pub struct Browser<Window: WindowMethods + 'static> {
     compositor: IOCompositor<Window>,
+    constellation_chan: Sender<ConstellationMsg>,
 }
 
 impl<Window> Browser<Window> where Window: WindowMethods + 'static {
     pub fn new(window: Rc<Window>) -> Browser<Window> {
         // Global configuration options, parsed from the command line.
         let opts = opts::get();
 
         script::init();
@@ -167,25 +174,26 @@ impl<Window> Browser<Window> where Windo
             }
         }
 
         // The compositor coordinates with the client window to create the final
         // rendered page and display it somewhere.
         let compositor = IOCompositor::create(window, InitialCompositorState {
             sender: compositor_proxy,
             receiver: compositor_receiver,
-            constellation_chan: constellation_chan,
+            constellation_chan: constellation_chan.clone(),
             time_profiler_chan: time_profiler_chan,
             mem_profiler_chan: mem_profiler_chan,
             webrender: webrender,
             webrender_api_sender: webrender_api_sender,
         });
 
         Browser {
             compositor: compositor,
+            constellation_chan: constellation_chan,
         }
     }
 
     pub fn handle_events(&mut self, events: Vec<WindowEvent>) -> bool {
         self.compositor.handle_events(events)
     }
 
     pub fn repaint_synchronously(&mut self) {
@@ -194,16 +202,28 @@ impl<Window> Browser<Window> where Windo
 
     pub fn pinch_zoom_level(&self) -> f32 {
         self.compositor.pinch_zoom_level()
     }
 
     pub fn request_title_for_main_frame(&self) {
         self.compositor.title_for_main_frame()
     }
+
+    pub fn setup_logging(&self) {
+        let constellation_chan = self.constellation_chan.clone();
+        log::set_logger(|max_log_level| {
+            let env_logger = EnvLogger::new();
+            let con_logger = FromCompositorLogger::new(constellation_chan);
+            let filter = max(env_logger.filter(), con_logger.filter());
+            let logger = BothLogger(env_logger, con_logger);
+            max_log_level.set(filter);
+            Box::new(logger)
+        }).expect("Failed to set logger.")
+    }
 }
 
 fn create_constellation(opts: opts::Opts,
                         compositor_proxy: Box<CompositorProxy + Send>,
                         time_profiler_chan: time::ProfilerChan,
                         mem_profiler_chan: mem::ProfilerChan,
                         devtools_chan: Option<Sender<devtools_traits::DevtoolsControlMsg>>,
                         supports_clipboard: bool,
@@ -239,27 +259,54 @@ fn create_constellation(opts: opts::Opts
 
     if let Some(url) = opts.url {
         constellation_chan.send(ConstellationMsg::InitLoadUrl(url)).unwrap();
     };
 
     constellation_chan
 }
 
+// A logger that logs to two downstream loggers.
+// This should probably be in the log crate.
+struct BothLogger<Log1, Log2>(Log1, Log2);
+
+impl<Log1, Log2> Log for BothLogger<Log1, Log2> where Log1: Log, Log2: Log {
+    fn enabled(&self, metadata: &LogMetadata) -> bool {
+        self.0.enabled(metadata) || self.1.enabled(metadata)
+    }
+
+    fn log(&self, record: &LogRecord) {
+        self.0.log(record);
+        self.1.log(record);
+    }
+}
+
+pub fn set_logger(constellation_chan: IpcSender<ScriptMsg>) {
+    log::set_logger(|max_log_level| {
+        let env_logger = EnvLogger::new();
+        let con_logger = FromScriptLogger::new(constellation_chan);
+        let filter = max(env_logger.filter(), con_logger.filter());
+        let logger = BothLogger(env_logger, con_logger);
+        max_log_level.set(filter);
+        Box::new(logger)
+    }).expect("Failed to set logger.")
+}
+
 /// Content process entry point.
 pub fn run_content_process(token: String) {
     let (unprivileged_content_sender, unprivileged_content_receiver) =
         ipc::channel::<UnprivilegedPipelineContent>().unwrap();
     let connection_bootstrap: IpcSender<IpcSender<UnprivilegedPipelineContent>> =
         IpcSender::connect(token).unwrap();
     connection_bootstrap.send(unprivileged_content_sender).unwrap();
 
     let unprivileged_content = unprivileged_content_receiver.recv().unwrap();
     opts::set_defaults(unprivileged_content.opts());
     PREFS.extend(unprivileged_content.prefs());
+    set_logger(unprivileged_content.constellation_chan());
 
     // Enter the sandbox if necessary.
     if opts::get().sandbox {
        create_sandbox();
     }
 
     script::init();
 
--- a/servo/components/servo/main.rs
+++ b/servo/components/servo/main.rs
@@ -17,17 +17,16 @@
 
 #![feature(start, core_intrinsics)]
 
 #[cfg(target_os = "android")]
 #[macro_use]
 extern crate android_glue;
 #[cfg(not(target_os = "android"))]
 extern crate backtrace;
-extern crate env_logger;
 // The window backed by glutin
 extern crate glutin_app as app;
 #[cfg(target_os = "android")]
 extern crate libc;
 #[cfg(target_os = "android")]
 #[macro_use]
 extern crate log;
 // The Servo engine
@@ -91,18 +90,16 @@ fn main() {
         if opts::get().is_running_problem_test && ::std::env::var("RUST_LOG").is_err() {
             ::std::env::set_var("RUST_LOG", "compositing::constellation");
         }
 
         None
     };
 
     initiate_panic_hook();
-    env_logger::init().unwrap();
-
     setup_logging();
 
     if let Some(token) = content_process_token {
         return servo::run_content_process(token)
     }
 
     if opts::get().is_printing_version {
         println!("{}", servo_version());
@@ -112,16 +109,18 @@ fn main() {
     let window = app::create_window(None);
 
     // Our wrapper around `Browser` that also implements some
     // callbacks required by the glutin window implementation.
     let mut browser = BrowserWrapper {
         browser: Browser::new(window.clone()),
     };
 
+    browser.browser.setup_logging();
+
     register_glutin_resize_handler(&window, &mut browser);
 
     browser.browser.handle_events(vec![WindowEvent::InitializeCompositing]);
 
     // Feed events from the window to the browser until the browser
     // says to stop.
     loop {
         let should_continue = browser.browser.handle_events(window.wait_events());
--- a/servo/ports/cef/Cargo.lock
+++ b/servo/ports/cef/Cargo.lock
@@ -311,16 +311,17 @@ dependencies = [
  "webrender 0.1.0 (git+https://github.com/servo/webrender)",
  "webrender_traits 0.2.0 (git+https://github.com/servo/webrender_traits)",
 ]
 
 [[package]]
 name = "constellation"
 version = "0.0.1"
 dependencies = [
+ "backtrace 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "canvas 0.0.1",
  "canvas_traits 0.0.1",
  "clipboard 0.1.2 (git+https://github.com/aweinstock314/rust-clipboard)",
  "compositing 0.0.1",
  "devtools_traits 0.0.1",
  "euclid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gaol 0.0.1 (git+https://github.com/servo/gaol)",
  "gfx 0.0.1",
@@ -583,17 +584,17 @@ name = "enum_primitive"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "num 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "env_logger"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "euclid"
@@ -1934,17 +1935,17 @@ dependencies = [
  "backtrace 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "browserhtml 0.1.15 (git+https://github.com/browserhtml/browserhtml?branch=gh-pages)",
  "canvas 0.0.1",
  "canvas_traits 0.0.1",
  "compositing 0.0.1",
  "constellation 0.0.1",
  "devtools 0.0.1",
  "devtools_traits 0.0.1",
- "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "env_logger 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gaol 0.0.1 (git+https://github.com/servo/gaol)",
  "gfx 0.0.1",
  "gleam 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "glutin_app 0.0.1",
  "ipc-channel 0.4.0 (git+https://github.com/servo/ipc-channel)",
  "layout 0.0.1",
  "layout_thread 0.0.1",
--- a/servo/ports/geckolib/Cargo.lock
+++ b/servo/ports/geckolib/Cargo.lock
@@ -1,15 +1,15 @@
 [root]
 name = "geckoservo"
 version = "0.0.1"
 dependencies = [
  "app_units 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "env_logger 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gecko_bindings 0.0.1",
  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -119,17 +119,17 @@ dependencies = [
 
 [[package]]
 name = "encoding_index_tests"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "env_logger"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "euclid"