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 <[email protected]> Authored: Fri Nov 29 13:00:53 2013 +0100 Committer: Sebastian Schaffert <[email protected]> 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> <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>
