Add the option to use JSON rather than GWT RPC in remote logging

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

Review by: fredsa@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8778 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/logging/Logging.gwt.xml b/user/src/com/google/gwt/logging/Logging.gwt.xml
index a3cc387..4646c37 100644
--- a/user/src/com/google/gwt/logging/Logging.gwt.xml
+++ b/user/src/com/google/gwt/logging/Logging.gwt.xml
@@ -13,6 +13,7 @@
 <!-- limitations under the License.                                         -->
 
 <module>
+  <inherits name='com.google.gwt.json.JSON'/>
   <inherits name="com.google.gwt.logging.LogImpl"/>
   <source path="client" />
   <source path="shared" />
diff --git a/user/src/com/google/gwt/logging/client/JsonLogRecordClientUtil.java b/user/src/com/google/gwt/logging/client/JsonLogRecordClientUtil.java
new file mode 100644
index 0000000..b210533
--- /dev/null
+++ b/user/src/com/google/gwt/logging/client/JsonLogRecordClientUtil.java
@@ -0,0 +1,88 @@
+/*
+ * 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.json.client.JSONArray;
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.logging.shared.SerializableLogRecord;
+import com.google.gwt.logging.shared.SerializableThrowable;
+
+/**
+ * A set of functions to convert SerializableLogRecords into JSON strings.
+ * The corresponding functions to convert them back are in
+ * JsonLogRecordServerUtil.java.  This class should only be used in client
+ * side code since it imports com.google.gwt.json.client classes.
+ * TODO(unnurg) once there is a unified JSON GWT library, combine this with
+ * JsonLogRecordServerUtil.
+ */
+public class JsonLogRecordClientUtil {
+
+  public static String serializableLogRecordAsJson(SerializableLogRecord slr) {
+    return serializableLogRecordAsJsonObject(slr).toString();
+  }
+  
+  private static JSONString getJsonString(String s) {
+    if (s == null) {
+      return new JSONString("");
+    }
+    return new JSONString(s);
+  }
+
+  private static JSONObject serializableLogRecordAsJsonObject(
+      SerializableLogRecord slr) {
+    JSONObject obj = new JSONObject();
+    obj.put("level", getJsonString(slr.getLevel()));
+    obj.put("loggerName", getJsonString(slr.getLoggerName()));
+    obj.put("msg", getJsonString(slr.getMsg()));
+    if (slr.getTimestamp() != null) {
+      obj.put("timestamp", new JSONString(slr.getTimestamp().toString()));
+    }
+    obj.put("thrown", serializableThrowableAsJsonObject(slr.getThrown()));
+    return obj;
+  }
+  
+  private static JSONObject serializableStackTraceElementAsJsonObject(
+      StackTraceElement e) {
+    JSONObject obj = new JSONObject();
+    if (e != null) {
+      obj.put("className", getJsonString(e.getClassName()));
+      obj.put("fileName", getJsonString(e.getFileName()));
+      obj.put("methodName", getJsonString(e.getMethodName()));
+      obj.put("lineNumber", getJsonString("" + e.getLineNumber()));
+    }
+    return obj;
+  }
+  
+  private static JSONObject serializableThrowableAsJsonObject(
+      SerializableThrowable t) {
+    JSONObject obj = new JSONObject();
+    if (t != null) {
+      obj.put("message", getJsonString(t.getMessage()));
+      obj.put("cause", serializableThrowableAsJsonObject(t.getCause()));
+      StackTraceElement[] stackTrace = t.getStackTrace();
+      if (stackTrace != null && stackTrace.length > 0) {
+        JSONArray arr = new JSONArray();
+        for (int i = 0; i < stackTrace.length; i++) {
+          arr.set(i, serializableStackTraceElementAsJsonObject(stackTrace[i]));
+        }
+        obj.put("stackTrace", arr);
+      }
+    }
+    return obj;
+  }
+}
diff --git a/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java b/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java
new file mode 100644
index 0000000..2173ddf
--- /dev/null
+++ b/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java
@@ -0,0 +1,87 @@
+/*
+ * 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.SerializableLogRecord;
+import com.google.gwt.logging.shared.SerializableThrowable;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+/**
+ * A set of functions to convert standard JSON strings into
+ * SerializableLogRecords. The corresponding functions to create the JSON
+ * strings are in JsonLogRecordClientUtil.java. This class should only be used
+ * in server side code since it imports org.json classes.
+ * TODO(unnurg) once there is a unified JSON GWT library, combine this with
+ * JsonLogRecordClientUtil.
+ */
+public class JsonLogRecordServerUtil {
+  public static SerializableLogRecord serializableLogRecordFromJson(
+      String jsonString) {
+    try {
+      JSONObject slr = new JSONObject(jsonString);
+      String level = slr.getString("level");
+      String loggerName = slr.getString("loggerName");
+      String msg = slr.getString("msg");
+      long timestamp = Long.parseLong(slr.getString("timestamp"));
+      SerializableThrowable thrown =
+        serializableThrowableFromJson(slr.getString("thrown"));
+      return new SerializableLogRecord(level, loggerName, msg, thrown, timestamp);
+    } catch (JSONException e) {
+    }
+    return null;
+  }
+  
+  private static StackTraceElement serializableStackTraceElementFromJson(
+      String jsonString) {
+    try {
+      JSONObject ste = new JSONObject(jsonString);
+      String className = ste.getString("className");
+      String fileName = ste.getString("fileName");
+      String methodName = ste.getString("methodName");
+      int lineNumber = Integer.parseInt(ste.getString("lineNumber"));
+      return new StackTraceElement(className, methodName, fileName,
+          lineNumber);
+    } catch (JSONException e) {
+    }
+    return null;
+  }
+  
+  private static SerializableThrowable serializableThrowableFromJson(
+      String jsonString) {
+    try {
+      JSONObject t = new JSONObject(jsonString);
+      String message = t.getString("message");
+      SerializableThrowable cause =
+        serializableThrowableFromJson(t.getString("cause"));
+      StackTraceElement[] stackTrace = null;
+      JSONArray st = t.getJSONArray("stackTrace");
+      if (st.length() > 0) {
+        stackTrace = new StackTraceElement[st.length()];
+        for (int i = 0; i < st.length(); i++) {
+          stackTrace[i] = serializableStackTraceElementFromJson(st.getString(i));
+        }
+      }
+      return new SerializableThrowable(message, cause, stackTrace);
+    } catch (JSONException e) {
+    }
+    return null;
+  }
+}
diff --git a/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java b/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java
index 28b5c65..92816f0 100644
--- a/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java
+++ b/user/src/com/google/gwt/logging/shared/SerializableLogRecord.java
@@ -31,13 +31,9 @@
   private String level;
   private String loggerName = "";
   private String msg;
-  private long timestamp;
   private SerializableThrowable thrown = null;
+  private long timestamp;
 
-  protected SerializableLogRecord() {
-    // for serialization
-  }
-  
   /**
    * Create a new SerializableLogRecord from a LogRecord.
    */
@@ -50,7 +46,28 @@
       thrown = new SerializableThrowable(lr.getThrown());
     }
   }
-
+  
+  public SerializableLogRecord(String level, String loggerName, String msg,
+      SerializableThrowable thrown, long timestamp) {
+    this.level = level;
+    this.loggerName = loggerName;
+    this.msg = msg;
+    this.timestamp = timestamp;
+    this.thrown = thrown;
+  }
+  
+  protected SerializableLogRecord() {
+    // for serialization
+  }
+  
+  public String getLevel() {
+    return level;
+  }
+  
+  public String getLoggerName() {
+    return loggerName;
+  }
+  
   /**
    * Create a new LogRecord from this SerializableLogRecord.
    */
@@ -63,4 +80,16 @@
     }
     return lr;
   }
+  
+  public String getMsg() {
+    return msg;
+  }
+  
+  public SerializableThrowable getThrown() {
+    return thrown;
+  }
+  
+  public Long getTimestamp() {
+    return timestamp;
+  }
 }
diff --git a/user/src/com/google/gwt/logging/shared/SerializableThrowable.java b/user/src/com/google/gwt/logging/shared/SerializableThrowable.java
index 5a90871..ebbe381 100644
--- a/user/src/com/google/gwt/logging/shared/SerializableThrowable.java
+++ b/user/src/com/google/gwt/logging/shared/SerializableThrowable.java
@@ -25,25 +25,44 @@
  * subclasses will cause the client side JS to be very big.
  */
 public class SerializableThrowable implements IsSerializable {
-  private String message;
   private SerializableThrowable cause = null;
+  private String message;
   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());
     }
+    stackTrace = t.getStackTrace();
+  }
+  
+  public SerializableThrowable(String message, SerializableThrowable cause,
+      StackTraceElement[] stackTrace) {
+    this.message = message;
+    this.cause = cause;
+    this.stackTrace = stackTrace;
+  }
+  
+  protected SerializableThrowable() {
+    // for serialization
+  }
+  
+  public SerializableThrowable getCause() {
+    return cause;
   }
 
+  public String getMessage() {
+    return message;
+  }
+  
+  public StackTraceElement[] getStackTrace() {
+    return stackTrace;
+  }
+  
   /**
    * Create a new Throwable from this SerializableThrowable.
    */
diff --git a/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java b/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
index 21a7252..7ae3819 100644
--- a/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
+++ b/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
@@ -16,6 +16,8 @@
 
 package com.google.gwt.requestfactory.client;
 
+import com.google.gwt.logging.client.JsonLogRecordClientUtil;
+import com.google.gwt.logging.shared.SerializableLogRecord;
 import com.google.gwt.requestfactory.shared.LoggingRequest;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.SyncResult;
@@ -87,16 +89,22 @@
     if (closed || !isLoggable(record)) {
       return;
     }
-    
     if (record.getLoggerName().contains(ignoredLoggerSubstring)) {
       return;
     }
-    
-    Receiver<Long> loggingReceiver = new LoggingReceiver();
-    requestProvider.getLoggingRequest().logMessage(
-        record.getLevel().toString(),
-        record.getLoggerName(),
-        record.getMessage()).fire(loggingReceiver);
+    SerializableLogRecord slr = new SerializableLogRecord(record);
+    String json = JsonLogRecordClientUtil.serializableLogRecordAsJson(slr);
+    requestProvider.getLoggingRequest().logMessage(json).fire(
+        new Receiver<Boolean>() {
+          @Override
+          public void onSuccess(Boolean response, Set<SyncResult> syncResults) {
+            if (!response) {
+              // A separate logger for wire activity, which does not get logged
+              // by the remote log handler, so we avoid infinite loops.
+              Logger wireLogger = Logger.getLogger("WireActivityLogger");
+              wireLogger.severe("Remote Logging failed to parse JSON");
+            }
+          }
+        });
   }
-
 }
diff --git a/user/src/com/google/gwt/requestfactory/server/Logging.java b/user/src/com/google/gwt/requestfactory/server/Logging.java
index 86f490c..fff5ebd 100644
--- a/user/src/com/google/gwt/requestfactory/server/Logging.java
+++ b/user/src/com/google/gwt/requestfactory/server/Logging.java
@@ -16,7 +16,10 @@
 
 package com.google.gwt.requestfactory.server;
 
-import java.util.logging.Level;
+import com.google.gwt.logging.server.JsonLogRecordServerUtil;
+import com.google.gwt.logging.shared.SerializableLogRecord;
+
+import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
 /**
@@ -25,20 +28,17 @@
  */
 public class Logging {
   private static Logger logger = Logger.getLogger(Logging.class.getName());
-  
-  public static Long logMessage(
-      String levelString, String loggerName, String originalMessage) {
-    Level level = Level.SEVERE;
-    try {
-      level = Level.parse(levelString);
-    } catch (IllegalArgumentException e) {
-      return 0L;
+
+  public static Boolean logMessage(String serializedLogRecordString) {
+    SerializableLogRecord slr =
+      JsonLogRecordServerUtil.serializableLogRecordFromJson(
+          serializedLogRecordString);
+    LogRecord lr = slr.getLogRecord();
+    if (lr == null) {
+      return false;
     }
-    String message = String.format("Client Side Logger: %s Message: %s",
-        loggerName, originalMessage);
-    
-    logger.log(level, message);
-    return 1L;
+    logger.log(lr);
+    return true;
   }
   
   private Long id = 0L;
diff --git a/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java b/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
index 6455c1b..cf07757 100644
--- a/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
@@ -25,11 +25,8 @@
 @Service(Logging.class)
 public interface LoggingRequest {
 
-  // Should be returning something better than a Long, but that's all that is
-  // supported for now, so using it as a boolean.
-  // Should also be passing something better than a series of strings, but
-  // that's the only possibility for now.
-  RequestObject<Long> logMessage(
-      String level, String loggerName, String message);
+  // TODO(unnurg): Pass a SerializableLogRecord here rather than it's
+  // serialized string.
+  RequestObject<Boolean> logMessage(String serializedLogRecordString);
  
 }