You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@marmotta.apache.org by ss...@apache.org on 2013/11/29 13:01:02 UTC

git commit: more logging configuration support (MARMOTTA-390)

Updated Branches:
  refs/heads/develop 5a6e37abd -> ac6f56d50


more logging configuration support (MARMOTTA-390)


Project: http://git-wip-us.apache.org/repos/asf/marmotta/repo
Commit: http://git-wip-us.apache.org/repos/asf/marmotta/commit/ac6f56d5
Tree: http://git-wip-us.apache.org/repos/asf/marmotta/tree/ac6f56d5
Diff: http://git-wip-us.apache.org/repos/asf/marmotta/diff/ac6f56d5

Branch: refs/heads/develop
Commit: ac6f56d50ade2173ef53b0031051a6e96af7ba68
Parents: 5a6e37a
Author: Sebastian Schaffert <ss...@apache.org>
Authored: Fri Nov 29 13:00:53 2013 +0100
Committer: Sebastian Schaffert <ss...@apache.org>
Committed: Fri Nov 29 13:00:53 2013 +0100

----------------------------------------------------------------------
 .../webservices/logging/LoggingWebService.java  | 219 +++++++++++++------
 .../src/main/resources/web/admin/js/logging.js  |  66 +++++-
 .../src/main/resources/web/admin/logging.html   |  75 +++----
 3 files changed, 247 insertions(+), 113 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/marmotta/blob/ac6f56d5/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/logging/LoggingWebService.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/logging/LoggingWebService.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/logging/LoggingWebService.java
index c44ab09..56ee852 100644
--- a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/logging/LoggingWebService.java
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/logging/LoggingWebService.java
@@ -29,6 +29,7 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -103,6 +104,38 @@ public class LoggingWebService {
     }
 
     /**
+     * Update all log appenders passed in the JSON list given in the body of the POST service request.
+     *
+     * @HTTP 200 appenders updated successfully
+     * @HTTP 400 appender configuration invalid (e.g. not proper JSON)
+     *
+     * @return HTTP status 200 in case of success
+     */
+    @POST
+    @Path("/appenders")
+    @Consumes("application/json")
+    public Response updateAppenders(@Context HttpServletRequest request) {
+        ObjectMapper mapper = new ObjectMapper();
+        try {
+            //log.info(getContentData(request.getReader()));
+            List<Map<String,Object>> values = mapper.readValue(request.getInputStream(), new TypeReference<ArrayList<HashMap<String,Object>>>(){});
+
+            for(Map<String,Object> module : values) {
+                if(module.get("id") != null) {
+                    updateAppenderJSON((String) module.get("id"), module);
+                }
+            }
+            return Response.ok("modules updated successfully").build();
+
+        } catch (JsonMappingException | JsonParseException e) {
+            return Response.status(Response.Status.BAD_REQUEST).entity("invalid JSON format: "+e.getMessage()).build();
+        } catch (IOException e) {
+            return Response.status(Response.Status.BAD_REQUEST).entity("could not read stream: "+e.getMessage()).build();
+        }
+    }
+
+
+    /**
      * Get the configuration of the log appender with the given ID using the JSON format described in the header of
      * the class
      *
@@ -144,57 +177,10 @@ public class LoggingWebService {
             //log.info(getContentData(request.getReader()));
             Map<String,Object> values = mapper.readValue(request.getInputStream(), new TypeReference<HashMap<String,Object>>(){});
 
-            String type  = (String) values.get("type");
-            String name  = (String) values.get("name");
-            String level   = (String) values.get("level");
-            String pattern = (String) values.get("pattern");
-
-            LoggingOutput appender = loggingService.getOutputConfiguration(id);
-            if(appender == null) {
-                // type information required
-                Preconditions.checkArgument(type != null, "appender type was not given");
-                Preconditions.checkArgument(name != null, "appender name was not given");
-
-                if("logfile".equals(type)) {
-                    String file = (String) values.get("file");
-
-                    Preconditions.checkArgument(file != null, "logfile name was not given");
-
-                    appender = loggingService.createLogFileOutput(id,name,file);
-                } else if("syslog".equals(type)) {
-                    String host = (String) values.get("host");
-
-                    Preconditions.checkArgument(host != null, "syslog host was not given");
-
-                    appender = loggingService.createSyslogOutput(id,name);
-                } else {
-                    return Response.status(Response.Status.NOT_IMPLEMENTED).entity("new appenders of type "+type+" not supported").build();
-                }
-            }
-
-            appender.setName(name);
-
-            if(level != null) {
-                appender.setMaxLevel(Level.toLevel(level));
-            }
-            if(pattern != null) {
-                appender.setPattern(pattern);
-            }
-            if(values.get("file") != null && appender instanceof LogFileOutput) {
-                ((LogFileOutput) appender).setFileName((String) values.get("file"));
-            }
-            if(values.get("keep") != null && appender instanceof LogFileOutput) {
-                ((LogFileOutput) appender).setKeepDays(Integer.parseInt(values.get("keep").toString()));
-            }
-            if(values.get("host") != null && appender instanceof SyslogOutput) {
-                ((SyslogOutput) appender).setHostName((String) values.get("host"));
-            }
-            if(values.get("facility") != null && appender instanceof SyslogOutput) {
-                ((SyslogOutput) appender).setFacility((String) values.get("facility"));
-            }
+            updateAppenderJSON(id,values);
 
             return Response.ok().build();
-        } catch (IllegalArgumentException ex) {
+        } catch (IllegalArgumentException | UnsupportedOperationException ex) {
             // thrown by Preconditions.checkArgument
             return Response.status(Response.Status.BAD_REQUEST).entity(ex.getMessage()).build();
         } catch (JsonMappingException | JsonParseException e) {
@@ -226,6 +212,45 @@ public class LoggingWebService {
     }
 
     /**
+     * Update all modules passed as JSON list argument to the POST body of the service call. Only the fields
+     * "level" and "appenders" can be updated for modules.
+     *
+     * @HTTP 200 modules updated successfully
+     * @HTTP 400 module configuration invalid (e.g. not proper JSON)
+     * @HTTP 404 module not found
+     *
+     * @return 200 OK in case modules have been updated successfully
+     */
+    @POST
+    @Path("/modules")
+    @Consumes("application/json")
+    public Response updateModules(@Context HttpServletRequest request) {
+        ObjectMapper mapper = new ObjectMapper();
+        try {
+            //log.info(getContentData(request.getReader()));
+            List<Map<String,Object>> values = mapper.readValue(request.getInputStream(), new TypeReference<ArrayList<HashMap<String,Object>>>(){});
+
+            boolean updated = false;
+            for(Map<String,Object> module : values) {
+                if(module.get("id") != null) {
+                    updated = updateModuleJSON((String) module.get("id"), module) || updated;
+                }
+            }
+            if(updated) {
+                return Response.ok("modules updated successfully").build();
+            } else {
+                return Response.status(Response.Status.NOT_FOUND).entity("one or more modules where not found").build();
+            }
+
+        } catch (JsonMappingException | JsonParseException e) {
+            return Response.status(Response.Status.BAD_REQUEST).entity("invalid JSON format: "+e.getMessage()).build();
+        } catch (IOException e) {
+            return Response.status(Response.Status.BAD_REQUEST).entity("could not read stream: "+e.getMessage()).build();
+        }
+    }
+
+
+    /**
      * Get the configuration of the logging module with the given id, using the JSON format described in the
      * header of this class.
      *
@@ -270,22 +295,13 @@ public class LoggingWebService {
             //log.info(getContentData(request.getReader()));
             Map<String,Object> values = mapper.readValue(request.getInputStream(), new TypeReference<HashMap<String,Object>>(){});
 
-            String level     = (String) values.get("level");
-            List   appenders = (List)   values.get("appenders");
-            for(LoggingModule module : loggingService.listModules()) {
-                if(StringUtils.equals(module.getId(), id)) {
-                    if(level != null) {
-                        module.setCurrentLevel(Level.toLevel(level));
-                    }
-                    if(appenders != null) {
-                        module.setLoggingOutputIds(appenders);
-                    }
-
-                    return Response.ok("module updated").build();
-                }
+
+            if(updateModuleJSON(id, values)) {
+                return Response.ok("module updated").build();
+            } else {
+                return Response.status(Response.Status.NOT_FOUND).build();
             }
 
-            return Response.status(Response.Status.NOT_FOUND).build();
         } catch (JsonMappingException | JsonParseException e) {
             return Response.status(Response.Status.BAD_REQUEST).entity("invalid JSON format: "+e.getMessage()).build();
         } catch (IOException e) {
@@ -294,6 +310,83 @@ public class LoggingWebService {
     }
 
 
+    /**
+     * Update a module following the JSON specification given as argument.
+     * @param spec
+     * @return
+     */
+    private boolean updateModuleJSON(String id, Map<String,Object> spec) {
+        String level     = (String) spec.get("level");
+        List   appenders = (List)   spec.get("appenders");
+        for(LoggingModule module : loggingService.listModules()) {
+            if(StringUtils.equals(module.getId(), id)) {
+                if(level != null) {
+                    module.setCurrentLevel(Level.toLevel(level));
+                }
+                if(appenders != null) {
+                    module.setLoggingOutputIds(appenders);
+                }
+
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    private void updateAppenderJSON(String id, Map<String,Object> values) {
+        String type  = (String) values.get("type");
+        String name  = (String) values.get("name");
+        String level   = (String) values.get("level");
+        String pattern = (String) values.get("pattern");
+
+        LoggingOutput appender = loggingService.getOutputConfiguration(id);
+        if(appender == null) {
+            // type information required
+            Preconditions.checkArgument(type != null, "appender type was not given");
+            Preconditions.checkArgument(name != null, "appender name was not given");
+
+            if("logfile".equals(type)) {
+                String file = (String) values.get("file");
+
+                Preconditions.checkArgument(file != null, "logfile name was not given");
+
+                appender = loggingService.createLogFileOutput(id,name,file);
+            } else if("syslog".equals(type)) {
+                String host = (String) values.get("host");
+
+                Preconditions.checkArgument(host != null, "syslog host was not given");
+
+                appender = loggingService.createSyslogOutput(id,name);
+            } else {
+                throw new UnsupportedOperationException("new appenders of type "+type+" not supported");
+            }
+        }
+
+        appender.setName(name);
+
+        if(level != null) {
+            appender.setMaxLevel(Level.toLevel(level));
+        }
+        if(pattern != null) {
+            appender.setPattern(pattern);
+        }
+        if(values.get("file") != null && appender instanceof LogFileOutput) {
+            ((LogFileOutput) appender).setFileName((String) values.get("file"));
+        }
+        if(values.get("keep") != null && appender instanceof LogFileOutput) {
+            ((LogFileOutput) appender).setKeepDays(Integer.parseInt(values.get("keep").toString()));
+        }
+        if(values.get("host") != null && appender instanceof SyslogOutput) {
+            ((SyslogOutput) appender).setHostName((String) values.get("host"));
+        }
+        if(values.get("facility") != null && appender instanceof SyslogOutput) {
+            ((SyslogOutput) appender).setFacility((String) values.get("facility"));
+        }
+
+    }
+
+
     private static Map<String,Object> appenderToJSON(LoggingOutput out) {
         Map<String,Object> result = new HashMap<>();
 

http://git-wip-us.apache.org/repos/asf/marmotta/blob/ac6f56d5/platform/marmotta-core/src/main/resources/web/admin/js/logging.js
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/resources/web/admin/js/logging.js b/platform/marmotta-core/src/main/resources/web/admin/js/logging.js
index eabceed..9620d5b 100644
--- a/platform/marmotta-core/src/main/resources/web/admin/js/logging.js
+++ b/platform/marmotta-core/src/main/resources/web/admin/js/logging.js
@@ -25,27 +25,33 @@
 var loggingApp = angular.module('logging', []);
 
 loggingApp.controller('LoggingController', function ($scope, $http) {
+    $scope.levels = ['OFF', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'];
+
+
     $http.get(url + 'logging/modules').success(function(data) {
         $scope.modules = data;
+
+        $scope.$watch('modules', function(newValue, oldValue) {
+            if(oldValue != newValue) {
+                $scope.needsModulesSave = true;
+            }
+        }, true);
     });
 
     $http.get(url + 'logging/appenders').success(function(data) {
         $scope.appenders = data;
-    });
 
-    $scope.updateAppender = function(appender) {
-        $http.post(url + 'logging/appenders/' + appender.id, appender);
-    };
-
-    $scope.updateModule = function(module) {
-        $http.post(url + 'logging/modules/' + module.id, module);
-    };
+        $scope.$watch('appenders', function(newValue, oldValue) {
+            if(oldValue != newValue) {
+                $scope.needsAppendersSave = true;
+            }
+        }, true);
+    });
 
     $scope.removeModuleAppender = function(module,appender_id) {
         var i = module.appenders.indexOf(appender_id);
         if(i >= 0) {
             module.appenders.splice(i,1);
-            $scope.updateModule(module);
         }
     };
 
@@ -53,7 +59,6 @@ loggingApp.controller('LoggingController', function ($scope, $http) {
         var i = module.appenders.indexOf(appender_id);
         if(i < 0) {
             module.appenders.push(appender_id);
-            $scope.updateModule(module);
         }
     };
 
@@ -66,4 +71,45 @@ loggingApp.controller('LoggingController', function ($scope, $http) {
         }
         return result;
     };
+
+    /**
+     * Save all appenders in this scope back to the Marmotta Webservice
+     */
+    $scope.saveAppenders = function() {
+        // $http.post takes the old model, so we use jQuery
+        $.ajax({
+            type:  "POST",
+            url:   url + 'logging/appenders',
+            data:  angular.toJson($scope.appenders),
+            contentType: 'application/json'
+        });
+
+        //$http.post(url + 'logging/appenders', $scope.appenders);
+        $scope.needsAppendersSave = false;
+    }
+
+
+    /**
+     * Save all modules in this scope back to the Marmotta Webservice
+     */
+    $scope.saveModules = function() {
+        // $http.post takes the old model, so we use jQuery
+        $.ajax({
+            type:  "POST",
+            url:   url + 'logging/modules',
+            data:  angular.toJson($scope.modules),
+            contentType: 'application/json'
+        });
+
+        //$http.post(url + 'logging/modules', mods);
+        $scope.needsModulesSave = false;
+    }
+
+    /*
+     * Watch updates to the model and set a flag to enable save buttons in UI
+     */
+    $scope.needsModulesSave = false;
+    $scope.needsAppendersSave = false;
+
+
 });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/marmotta/blob/ac6f56d5/platform/marmotta-core/src/main/resources/web/admin/logging.html
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/resources/web/admin/logging.html b/platform/marmotta-core/src/main/resources/web/admin/logging.html
index dd4ab0d..577f173 100644
--- a/platform/marmotta-core/src/main/resources/web/admin/logging.html
+++ b/platform/marmotta-core/src/main/resources/web/admin/logging.html
@@ -90,6 +90,24 @@
 
         <h1>Marmotta Logging Configuration</h1>
 
+        <p>
+            The following forms allow to configure logging in Apache Marmotta. Logging in Marmotta is based on
+            <a href="http://logback.qos.ch/">Logback</a> and uses the following concepts:
+        </p>
+
+        <ul>
+            <li>
+                <strong>appenders</strong> are log output destinations (either the console, a logfile, or a syslog facility);
+                each appender has at least a pattern describing the layout (following the
+                <a href="http://logback.qos.ch/manual/layouts.html#ClassicPatternLayout">Logback Pattern Layout</a>) and a maximum
+                level; messages with higher level are always ignored by the appender
+            </li>
+            <li>
+                <strong>modules</strong> are logical components in Marmotta that represent a certain functionality (e.g. "SPARQL");
+                each module has a loglevel and a list of appenders to which it will send its output.
+            </li>
+        </ul>
+
         <div ng-app="logging" ng-controller="LoggingController">
 
             <h2>Log Appenders</h2>
@@ -98,20 +116,14 @@
             <h3>Console</h3>
             <table class="appenders">
                 <tr>
-                    <th>Name</th><th>Level</th><th>Pattern</th><th></th><th></th>
+                    <th>Name</th><th>Level</th><th>Pattern <a href="http://logback.qos.ch/manual/layouts.html#ClassicPatternLayout" target="new">?</a> </th><th></th><th></th>
                 </tr>
                 <tr ng-repeat="appender in appenders | filter:{type:'console'}" ng-class-even="'even'" ng-class-odd="'odd'">
                     <td class="name">{{appender.name}}</td>
                     <td class="level">
-                        <select ng-model="appender.level" ng-change="updateAppender(appender)">
-                            <option>ERROR</option>
-                            <option>WARN</option>
-                            <option>INFO</option>
-                            <option>DEBUG</option>
-                            <option>TRACE</option>
-                        </select>
+                        <select ng-model="appender.level" ng-options="level for level in levels"></select>
                     </td>
-                    <td class="pattern">{{appender.pattern}}</td>
+                    <td class="pattern"><input type="text" ng-model="appender.pattern" size="40"/> </td>
                     <td class="additional"></td>
                     <td class="additional"></td>
                 </tr>
@@ -120,45 +132,33 @@
             <h3>Logfile</h3>
             <table class="appenders">
                 <tr>
-                    <th>Name</th><th>Level</th><th>Pattern</th><th>Filename</th><th>Keep Days</th>
+                    <th>Name</th><th>Level</th><th>Pattern <a href="http://logback.qos.ch/manual/layouts.html#ClassicPatternLayout" target="new">?</a> </th><th>Filename</th><th>Keep Days</th>
                 </tr>
                 <tr ng-repeat="appender in appenders | filter:{type:'logfile'}" ng-class-even="'even'" ng-class-odd="'odd'">
                     <td class="name">{{appender.name}}</td>
                     <td class="level">
-                        <select ng-model="appender.level" ng-change="updateAppender(appender)">
-                            <option>ERROR</option>
-                            <option>WARN</option>
-                            <option>INFO</option>
-                            <option>DEBUG</option>
-                            <option>TRACE</option>
-                        </select>
+                        <select ng-model="appender.level" ng-options="level for level in levels"></select>
                     </td>
-                    <td class="pattern">{{appender.pattern}}</td>
-                    <td class="additional">{{appender.file}}</td>
-                    <td class="additional">{{appender.keep}}</td>
+                    <td class="pattern"><input type="text" ng-model="appender.pattern" size="40"/></td>
+                    <td class="additional"><input type="text" ng-model="appender.file" size="20"/></td>
+                    <td class="additional"><input type="number" ng-model="appender.keep" size="3" min="0"/></td>
                 </tr>
             </table>
 
             <h3>Syslog</h3>
             <table class="appenders">
                 <tr>
-                    <th>Name</th><th>Level</th><th>Pattern</th><th>Host</th><th>Facility</th>
+                    <th>Name</th><th>Level</th><th>Pattern <a href="http://logback.qos.ch/manual/layouts.html#ClassicPatternLayout" target="new">?</a> </th><th>Host</th><th>Facility</th>
                 </tr>
                 <tr ng-repeat="appender in appenders | filter:{type:'syslog'}" ng-class-even="'even'" ng-class-odd="'odd'">
                     <td class="name">{{appender.name}}</td>
                     <td class="level">
-                        <select ng-model="appender.level" ng-change="updateAppender(appender)">
-                            <option>ERROR</option>
-                            <option>WARN</option>
-                            <option>INFO</option>
-                            <option>DEBUG</option>
-                            <option>TRACE</option>
-                        </select>
+                        <select ng-model="appender.level" ng-options="level for level in levels"></select>
                     </td>
-                    <td class="pattern">{{appender.pattern}}</td>
-                    <td class="additional">{{appender.host}}</td>
+                    <td class="pattern"><input type="text" ng-model="appender.pattern" size="40"/></td>
+                    <td class="additional"><input type="text" ng-model="appender.host" size="20"/></td>
                     <td class="additional">
-                        <select ng-model="appender.facility" ng-change="updateAppender(appender)">
+                        <select ng-model="appender.facility">
                             <option>USER</option>
                             <option>DAEMON</option>
                             <option>SYSLOG</option>
@@ -175,6 +175,7 @@
                 </tr>
             </table>
 
+            <div class="save-button"><button ng-click="saveAppenders()" ng-disabled="!needsAppendersSave">Save</button> </div>
 
 
             <h2>Log Modules</h2>
@@ -186,13 +187,7 @@
                 <tr ng-repeat="module in modules" ng-class-even="'even'" ng-class-odd="'odd'">
                     <td class="name">{{module.name}}</td>
                     <td class="level">
-                        <select ng-model="module.level" ng-change="updateModule(module)">
-                            <option>ERROR</option>
-                            <option>WARN</option>
-                            <option>INFO</option>
-                            <option>DEBUG</option>
-                            <option>TRACE</option>
-                        </select>
+                        <select ng-model="module.level" ng-options="level for level in levels"></select>
                     </td>
                     <td class="appenders">
                         <span ng-repeat="appender in module.appenders">
@@ -203,12 +198,12 @@
                     <td class="actions">
                         <select ng-model="module.new_appender" style="width: 80%">
                             <option ng-repeat="appender in getUnselectedModuleAppenders(module)" value="{{appender.id}}">{{appender.name}}</option>
-                        </select>
-                        <button ng-click="addModuleAppender(module,module.new_appender)">+</button>
+                        </select>&nbsp;<button ng-click="addModuleAppender(module,module.new_appender)">+</button>
                     </td>
                 </tr>
             </table>
 
+            <div class="save-button"><button ng-click="saveModules()" ng-disabled="!needsModulesSave">Save</button> </div>
 
         </div>