Add a simple remote log handler, and update the sample to use it

Review at http://gwt-code-reviews.appspot.com/626802

Review by: fredsa@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8328 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/logexample/src/com/google/gwt/sample/logexample/client/HandlerController.java b/samples/logexample/src/com/google/gwt/sample/logexample/client/HandlerController.java
index 9c812b4..60ced41 100644
--- a/samples/logexample/src/com/google/gwt/sample/logexample/client/HandlerController.java
+++ b/samples/logexample/src/com/google/gwt/sample/logexample/client/HandlerController.java
@@ -23,6 +23,7 @@
 import com.google.gwt.logging.client.DevelopmentModeLogHandler;
 import com.google.gwt.logging.client.FirebugLogHandler;
 import com.google.gwt.logging.client.HasWidgetsLogHandler;
+import com.google.gwt.logging.client.SimpleRemoteLogHandler;
 import com.google.gwt.logging.client.SystemLogHandler;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
@@ -66,6 +67,7 @@
   @UiField CheckBox firebugCheckbox;
   @UiField CheckBox popupCheckbox;
   @UiField CheckBox systemCheckbox;
+  @UiField CheckBox remoteCheckbox;
   private Map<String, Handler> handlers;
   private Logger logger;
   private Panel panel;
@@ -86,6 +88,7 @@
     setupHandler(DevelopmentModeLogHandler.class.getName(), devmodeCheckbox);
     setupHandler(FirebugLogHandler.class.getName(), firebugCheckbox);
     setupHandler(HasWidgetsLogHandler.class.getName(), popupCheckbox);
+    setupHandler(SimpleRemoteLogHandler.class.getName(), remoteCheckbox);
   }
   
   public Panel getPanel() {
diff --git a/samples/logexample/src/com/google/gwt/sample/logexample/client/HandlerController.ui.xml b/samples/logexample/src/com/google/gwt/sample/logexample/client/HandlerController.ui.xml
index 08e790e..9dc9421 100644
--- a/samples/logexample/src/com/google/gwt/sample/logexample/client/HandlerController.ui.xml
+++ b/samples/logexample/src/com/google/gwt/sample/logexample/client/HandlerController.ui.xml
@@ -13,5 +13,6 @@
     <br/>
     <g:CheckBox ui:field='firebugCheckbox'> Firebug Handler </g:CheckBox>
     <g:CheckBox ui:field='popupCheckbox'> Popup Handler </g:CheckBox>
+    <g:CheckBox ui:field='remoteCheckbox'> Remote Handler </g:CheckBox>
   </g:HTMLPanel>
 </ui:UiBinder>
\ No newline at end of file
diff --git a/samples/logexample/war/WEB-INF/web.xml b/samples/logexample/war/WEB-INF/web.xml
new file mode 100644
index 0000000..6ff5449
--- /dev/null
+++ b/samples/logexample/war/WEB-INF/web.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app>
+
+  <!-- Servlets -->
+  <servlet>
+    <servlet-name>logServlet</servlet-name>
+    <servlet-class>com.google.gwt.sample.logexample.server.LoggingServiceImpl</servlet-class>
+  </servlet>
+  
+  <servlet-mapping>
+    <servlet-name>logServlet</servlet-name>
+    <url-pattern>/logexample/log</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
+    <servlet-name>remoteLoggingServlet</servlet-name>
+    <servlet-class>com.google.gwt.logging.server.RemoteLoggingServiceImpl</servlet-class>
+  </servlet>
+  
+  <servlet-mapping>
+    <servlet-name>remoteLoggingServlet</servlet-name>
+    <url-pattern>logexample/remote_logging</url-pattern>
+  </servlet-mapping>
+
+  
+  <!-- Default page to serve -->
+  <welcome-file-list>
+    <welcome-file>LogExample.html</welcome-file>
+  </welcome-file-list>
+
+</web-app>
diff --git a/user/src/com/google/gwt/logging/Logging.gwt.xml b/user/src/com/google/gwt/logging/Logging.gwt.xml
index 48adb38..c22ccc4 100644
--- a/user/src/com/google/gwt/logging/Logging.gwt.xml
+++ b/user/src/com/google/gwt/logging/Logging.gwt.xml
@@ -12,9 +12,10 @@
 <!-- implied. License for the specific language governing permissions and   -->
 <!-- limitations under the License.                                         -->
 
-<!-- regular expressions support.                                           -->
 <module>
   <inherits name="com.google.gwt.logging.LogImpl"/>
+  <source path="client" />
+  <source path="shared" />
   
   <replace-with class="com.google.gwt.logging.client.LogConfiguration.LogConfigurationImplRegular">
     <when-type-is class="com.google.gwt.logging.client.LogConfiguration.LogConfigurationImplNull"/>
@@ -79,6 +80,11 @@
     <when-type-is class="com.google.gwt.logging.client.SystemLogHandler" />
     <when-property-is name="gwt.logging.systemHandler" value="DISABLED" />
   </replace-with>
+  <define-property name="gwt.logging.simpleRemoteHandler" values="ENABLED, DISABLED" />
+  <replace-with class="com.google.gwt.logging.client.NullLogHandler">
+    <when-type-is class="com.google.gwt.logging.client.SimpleRemoteLogHandler" />
+    <when-property-is name="gwt.logging.simpleRemoteHandler" value="DISABLED" />
+  </replace-with>
   <define-property name="gwt.logging.popupHandler" values="ENABLED, DISABLED" />
   <replace-with class="com.google.gwt.logging.client.NullLoggingPopup">
     <when-type-is class="com.google.gwt.logging.client.LoggingPopup" />
@@ -93,6 +99,7 @@
   <set-property name="gwt.logging.firebugHandler" value="ENABLED" />
   <set-property name="gwt.logging.popupHandler" value="ENABLED" />
   <set-property name="gwt.logging.systemHandler" value="ENABLED" />
+  <set-property name="gwt.logging.simpleRemoteHandler" value="ENABLED" />
   
   <entry-point class="com.google.gwt.logging.client.LogConfiguration"/>
 </module>
\ No newline at end of file
diff --git a/user/src/com/google/gwt/logging/client/LogConfiguration.java b/user/src/com/google/gwt/logging/client/LogConfiguration.java
index e08b93a..7007a89 100644
--- a/user/src/com/google/gwt/logging/client/LogConfiguration.java
+++ b/user/src/com/google/gwt/logging/client/LogConfiguration.java
@@ -98,6 +98,8 @@
       addHandlerIfNotNull(l, firebug);
       Handler system = GWT.create(SystemLogHandler.class);
       addHandlerIfNotNull(l, system);
+      Handler remote = GWT.create(SimpleRemoteLogHandler.class);
+      addHandlerIfNotNull(l, remote);
       HasWidgets loggingWidget = GWT.create(LoggingPopup.class);
       if (!(loggingWidget instanceof NullLoggingPopup)) {
         addHandlerIfNotNull(l, new HasWidgetsLogHandler(loggingWidget));
diff --git a/user/src/com/google/gwt/logging/client/SimpleRemoteLogHandler.java b/user/src/com/google/gwt/logging/client/SimpleRemoteLogHandler.java
new file mode 100644
index 0000000..3970e19
--- /dev/null
+++ b/user/src/com/google/gwt/logging/client/SimpleRemoteLogHandler.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.logging.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.logging.shared.RemoteLoggingService;
+import com.google.gwt.logging.shared.RemoteLoggingServiceAsync;
+import com.google.gwt.logging.shared.SerializableLogRecord;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+/**
+ * A very simple handler which sends messages to the server via GWT RPC to be
+ * logged. Note that this logger should not be used in production. It does not
+ * do any intelligent batching of RPC's, nor does it disable when the RPC
+ * calls fail repeatedly.
+ */
+public final class SimpleRemoteLogHandler extends Handler {
+  private static Logger logger =
+    Logger.getLogger(SimpleRemoteLogHandler.class.getName());
+
+  class DefaultCallback implements AsyncCallback<String> {
+    public void onFailure(Throwable caught) {
+      logger.severe("Remote logging failed: " + caught.toString());
+    }
+    public void onSuccess(String result) {
+      if (!result.isEmpty()) {
+        logger.severe("Remote logging failed: " + result);
+      } else {
+        logger.finest("Remote logging message acknowledged");
+      }
+    }
+  }
+  
+  private RemoteLoggingServiceAsync service;
+  private AsyncCallback<String> callback;
+
+  public SimpleRemoteLogHandler() {
+    service = (RemoteLoggingServiceAsync) GWT.create(RemoteLoggingService.class);
+    this.callback = new DefaultCallback();
+  }
+  
+  @Override
+  public void close() {
+    // No action needed
+  }
+
+  @Override
+  public void flush() {
+    // No action needed
+  }
+
+  @Override
+  public void publish(LogRecord record) {
+    if (record.getLoggerName().equals(logger.getName())) {
+      // We don't want to propagate our own messages to the server since it
+      // would lead to an infinite loop.
+      return;
+    }
+    service.logOnServer(new SerializableLogRecord(record), callback);
+  }
+}
diff --git a/user/src/com/google/gwt/logging/server/RemoteLoggingServiceImpl.java b/user/src/com/google/gwt/logging/server/RemoteLoggingServiceImpl.java
new file mode 100644
index 0000000..b68f281
--- /dev/null
+++ b/user/src/com/google/gwt/logging/server/RemoteLoggingServiceImpl.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.logging.server;
+
+import com.google.gwt.logging.shared.RemoteLoggingService;
+import com.google.gwt.logging.shared.SerializableLogRecord;
+import com.google.gwt.user.server.rpc.RemoteServiceServlet;
+
+import java.util.logging.Logger;
+
+/**
+ * Server side code for the remote log handler.
+ */
+public class RemoteLoggingServiceImpl extends RemoteServiceServlet implements
+    RemoteLoggingService {
+  private static Logger logger = Logger.getLogger("gwt.remote");
+
+  public final String logOnServer(SerializableLogRecord record) {
+    try {
+      logger.log(record.getLogRecord());
+    } catch (RuntimeException e) {
+      String exceptionString = e.toString();
+      String failureMessage = "Failed to log message due to " + exceptionString;
+      System.err.println(failureMessage);
+      e.printStackTrace();
+      
+      // Return the exception description so that the client code can
+      // print or log it if it wants.
+      return e.toString();
+    }
+    return "";
+  }
+}
diff --git a/user/src/com/google/gwt/logging/shared/RemoteLoggingService.java b/user/src/com/google/gwt/logging/shared/RemoteLoggingService.java
new file mode 100644
index 0000000..2017dc0
--- /dev/null
+++ b/user/src/com/google/gwt/logging/shared/RemoteLoggingService.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.logging.shared;
+
+import com.google.gwt.user.client.rpc.RemoteService;
+import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
+
+/**
+ * The client side stub for the logging RPC service.
+ */
+@RemoteServiceRelativePath("remote_logging")
+public interface RemoteLoggingService extends RemoteService {
+  String logOnServer(SerializableLogRecord record);
+}
diff --git a/user/src/com/google/gwt/logging/shared/RemoteLoggingServiceAsync.java b/user/src/com/google/gwt/logging/shared/RemoteLoggingServiceAsync.java
new file mode 100644
index 0000000..4d10df1
--- /dev/null
+++ b/user/src/com/google/gwt/logging/shared/RemoteLoggingServiceAsync.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.logging.shared;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * The async counterpart of <code>RemoteLoggingService</code>.
+ */
+public interface RemoteLoggingServiceAsync {
+  void logOnServer(SerializableLogRecord record, AsyncCallback<String> callback);
+}
diff --git a/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java b/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java
new file mode 100644
index 0000000..28b5c65
--- /dev/null
+++ b/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.logging.shared;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+/**
+ * A representation of a LogRecord which can be used by GWT RPC. In addition
+ * to the fact that LogRecord is not serializable, it has different
+ * implementations on the server and client side, so we cannot pass it over
+ * the wire directly.
+ */
+public class SerializableLogRecord implements IsSerializable {
+  private String level;
+  private String loggerName = "";
+  private String msg;
+  private long timestamp;
+  private SerializableThrowable thrown = null;
+
+  protected SerializableLogRecord() {
+    // for serialization
+  }
+  
+  /**
+   * Create a new SerializableLogRecord from a LogRecord.
+   */
+  public SerializableLogRecord(LogRecord lr) {
+    level = lr.getLevel().toString();
+    loggerName = lr.getLoggerName();
+    msg = lr.getMessage();
+    timestamp = lr.getMillis();
+    if (lr.getThrown() != null) {
+      thrown = new SerializableThrowable(lr.getThrown());
+    }
+  }
+
+  /**
+   * Create a new LogRecord from this SerializableLogRecord.
+   */
+  public LogRecord getLogRecord() {
+    LogRecord lr = new LogRecord(Level.parse(level), msg);
+    lr.setLoggerName(loggerName);
+    lr.setMillis(timestamp);
+    if (thrown != null) {
+      lr.setThrown(thrown.getThrowable());
+    }
+    return lr;
+  }
+}
diff --git a/user/src/com/google/gwt/logging/shared/SerializableThrowable.java b/user/src/com/google/gwt/logging/shared/SerializableThrowable.java
new file mode 100644
index 0000000..5a90871
--- /dev/null
+++ b/user/src/com/google/gwt/logging/shared/SerializableThrowable.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.logging.shared;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+/**
+ * A representation of a Throwable which can be used by GWT RPC. Although
+ * Throwables are serializable, we don't want to use them directly in the
+ * SerializableLogRecord since including a class with so many possible
+ * subclasses will cause the client side JS to be very big.
+ */
+public class SerializableThrowable implements IsSerializable {
+  private String message;
+  private SerializableThrowable cause = null;
+  private StackTraceElement[] stackTrace;
+
+  protected SerializableThrowable() {
+    // for serialization
+  }
+  
+  /**
+   * Create a new SerializableThrowable from a Throwable.
+   */
+  public SerializableThrowable(Throwable t) {
+    message = t.getMessage();
+    stackTrace = t.getStackTrace();
+    if (t.getCause() != null) {
+      cause = new SerializableThrowable(t.getCause());
+    }
+  }
+
+  /**
+   * Create a new Throwable from this SerializableThrowable.
+   */
+  public Throwable getThrowable() {
+    Throwable t;
+    if (cause != null) {
+      t = new Throwable(message, cause.getThrowable());
+    } else {
+      t = new Throwable(message);
+    }
+    t.setStackTrace(stackTrace);
+    return t;
+  }
+}