Log browser based on osgi LogService
Signed-off-by: Lukasz Dywicki <luke@code-house.org>
git-svn-id: https://svn.apache.org/repos/asf/karaf/webconsole/trunk@1166781 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/LogEntriesDataProvider.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/LogEntriesDataProvider.java
new file mode 100644
index 0000000..cdca34c
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/LogEntriesDataProvider.java
@@ -0,0 +1,72 @@
+package org.apache.karaf.webconsole.osgi.internal.log;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.karaf.webconsole.osgi.internal.log.search.Matcher;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.model.IModel;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogReaderService;
+
+final class LogEntriesDataProvider extends SortableDataProvider<LogEntry> {
+
+ private LogReaderService logReader;
+
+ private List<LogEntry> entries;
+
+ private Options options;
+
+ private transient List<Matcher> matchers;
+
+ public LogEntriesDataProvider(LogReaderService logReader, Options options, List<Matcher> matchers) {
+ this.logReader = logReader;
+ this.options = options;
+ this.matchers = matchers;
+ }
+
+ public Iterator<? extends LogEntry> iterator(int first, int count) {
+ return getEntries().subList(first, first + count).iterator();
+ }
+
+ private List<LogEntry> getEntries() {
+ if (entries == null) {
+ entries = new LinkedList<LogEntry>();
+ @SuppressWarnings("unchecked")
+ Enumeration<LogEntry> logEntries = logReader.getLog();
+ while (logEntries.hasMoreElements()) {
+ LogEntry entry = logEntries.nextElement();
+
+ boolean matchesAll = true;
+ for (Matcher matcher : matchers) {
+ if (!matcher.matches(entry, options)) {
+ matchesAll = false;
+ break;
+ }
+ }
+
+ if (matchesAll) entries.add(entry);
+ }
+ }
+ return entries;
+ }
+
+ public IModel<LogEntry> model(LogEntry object) {
+ return new LogEntryModel(this.logReader, object);
+ }
+
+ public int size() {
+ return getEntries().size();
+ }
+
+ public void setOptions(Options options) {
+ this.options = options;
+ }
+
+ @Override
+ public void detach() {
+ entries = null;
+ }
+}
\ No newline at end of file
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/LogEntryModel.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/LogEntryModel.java
new file mode 100644
index 0000000..9b881cb
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/LogEntryModel.java
@@ -0,0 +1,30 @@
+package org.apache.karaf.webconsole.osgi.internal.log;
+
+import java.util.Collections;
+import java.util.Enumeration;
+
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogReaderService;
+
+public class LogEntryModel extends LoadableDetachableModel<LogEntry> {
+
+ private int hashCode;
+ private LogReaderService logReader;
+
+ public LogEntryModel(LogReaderService logReader, LogEntry object) {
+ super(object);
+ this.logReader = logReader;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected LogEntry load() {
+ for (LogEntry entry : Collections.list((Enumeration<LogEntry>) logReader.getLog())) {
+ if (hashCode == entry.hashCode()) {
+ return entry;
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/LogsPage.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/LogsPage.java
new file mode 100644
index 0000000..414680f
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/LogsPage.java
@@ -0,0 +1,77 @@
+package org.apache.karaf.webconsole.osgi.internal.log;
+
+import java.text.DateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.karaf.webconsole.core.table.PropertyColumnExt;
+import org.apache.karaf.webconsole.osgi.internal.OsgiPage;
+import org.apache.karaf.webconsole.osgi.internal.log.search.BundleMatcher;
+import org.apache.karaf.webconsole.osgi.internal.log.search.DateFromMatcher;
+import org.apache.karaf.webconsole.osgi.internal.log.search.DateToMatcher;
+import org.apache.karaf.webconsole.osgi.internal.log.search.Matcher;
+import org.apache.karaf.webconsole.osgi.internal.log.search.MessageMatcher;
+import org.apache.karaf.webconsole.osgi.internal.log.search.PriorityMatcher;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.DefaultDataTable;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+import org.ops4j.pax.wicket.api.PaxWicketBean;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogReaderService;
+
+public class LogsPage extends OsgiPage {
+
+ @PaxWicketBean(name = "logReader")
+ private LogReaderService logReader;
+
+ private Options options = new Options();
+
+ public LogsPage() {
+ CompoundPropertyModel<Options> model = new CompoundPropertyModel<Options>(new PropertyModel<Options>(this, "options"));
+ setDefaultModel(model);
+
+ @SuppressWarnings("unchecked")
+ IColumn<LogEntry>[] columns = new IColumn[] {
+ new AbstractColumn<LogEntry>(Model.of("time")) {
+ public void populateItem(Item<ICellPopulator<LogEntry>> cellItem, String componentId, IModel<LogEntry> rowModel) {
+ long time = rowModel.getObject().getTime();
+ DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.FULL);
+ cellItem.add(new Label(componentId, format.format(new Date(time))));
+ }
+ },
+ new AbstractColumn<LogEntry>(Model.of("level")) {
+ public void populateItem(Item<ICellPopulator<LogEntry>> cellItem, String componentId, IModel<LogEntry> rowModel) {
+ cellItem.add(new Label(componentId, Priority.valueOf(rowModel.getObject()).name()));
+ }
+ },
+ new PropertyColumnExt<LogEntry>("Bundle", "bundle.symbolicName"),
+ new PropertyColumnExt<LogEntry>("Version", "bundle.version"),
+ new PropertyColumnExt<LogEntry>("Message", "message"),
+ new PropertyColumnExt<LogEntry>("Exception", "exception"),
+ };
+
+ OptionsForm form = new OptionsForm("filters", model);
+
+ List<Matcher> matchers = Arrays.asList(
+ new PriorityMatcher(),
+ new MessageMatcher(),
+ new BundleMatcher(),
+ new DateFromMatcher(),
+ new DateToMatcher()
+ );
+
+ LogEntriesDataProvider provider = new LogEntriesDataProvider(logReader, options, matchers);
+ DefaultDataTable<LogEntry> table = new DefaultDataTable<LogEntry>("logs", columns, provider, 20);
+
+ add(table);
+ add(form);
+ }
+}
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/Options.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/Options.java
new file mode 100644
index 0000000..93917cf
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/Options.java
@@ -0,0 +1,66 @@
+package org.apache.karaf.webconsole.osgi.internal.log;
+
+import java.io.Serializable;
+
+public class Options implements Serializable {
+
+ private Priority priority = Priority.Any;
+
+ private String bundleNameFragment;
+
+ private String messageFragment;
+
+ private Long dateFrom = null;
+
+ private Long dateTo = null;
+
+ public Priority getPriority() {
+ return priority;
+ }
+
+ public void setPriority(Priority priority) {
+ this.priority = priority;
+ }
+
+ public String getBundleNameFragment() {
+ return bundleNameFragment;
+ }
+
+ public void setBundleNameFragment(String bundleNameFragment) {
+ this.bundleNameFragment = bundleNameFragment;
+ }
+
+ public String getMessageFragment() {
+ return messageFragment;
+ }
+
+ public void setMessageFragment(String messageFragment) {
+ this.messageFragment = messageFragment;
+ }
+
+ public Long getDateFrom() {
+ return dateFrom;
+ }
+
+ public void setDateFrom(Long dateFrom) {
+ this.dateFrom = dateFrom;
+ }
+
+ public Long getDateTo() {
+ return dateTo;
+ }
+
+ public void setDateTo(Long dateTo) {
+ this.dateTo = dateTo;
+ }
+
+ @Override
+ public String toString() {
+ return "Options [priority=" + priority + ", bundleNameFragment="
+ + bundleNameFragment + ", messageFragment=" + messageFragment
+ + ", dateFrom=" + dateFrom + ", dateTo=" + dateTo + "]";
+ }
+
+
+
+}
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/OptionsForm.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/OptionsForm.java
new file mode 100644
index 0000000..20f9898
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/OptionsForm.java
@@ -0,0 +1,42 @@
+package org.apache.karaf.webconsole.osgi.internal.log;
+
+import java.util.Arrays;
+
+import org.apache.wicket.extensions.markup.html.form.select.IOptionRenderer;
+import org.apache.wicket.extensions.markup.html.form.select.Select;
+import org.apache.wicket.extensions.markup.html.form.select.SelectOptions;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.SubmitLink;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+
+public class OptionsForm extends Form<Options> {
+
+ public OptionsForm(String id, CompoundPropertyModel<Options> model) {
+ super(id, model);
+
+ IModel<Priority> priority = model.bind("priority");
+ Select<Priority> select = new Select<Priority>("priority", priority);
+ select.add(new SelectOptions<Priority>("options", Arrays.asList(Priority.values()), new IOptionRenderer<Priority>() {
+ public String getDisplayValue(Priority object) {
+ return object.name();
+ }
+
+ public IModel<Priority> getModel(Priority value) {
+ return Model.of(value);
+ }
+ }));
+
+ add(select);
+ add(new TextField<Long>("dateFrom", Long.class));
+ add(new TextField<Long>("dateTo", Long.class));
+ add(new TextField<String>("messageFragment",String.class));
+ add(new TextField<String>("bundleNameFragment",String.class));
+
+
+ add(new SubmitLink("submit"));
+ }
+
+}
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/Priority.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/Priority.java
new file mode 100644
index 0000000..e88145d
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/Priority.java
@@ -0,0 +1,40 @@
+package org.apache.karaf.webconsole.osgi.internal.log;
+
+import static org.osgi.service.log.LogService.LOG_DEBUG;
+import static org.osgi.service.log.LogService.LOG_ERROR;
+import static org.osgi.service.log.LogService.LOG_INFO;
+import static org.osgi.service.log.LogService.LOG_WARNING;
+
+import org.osgi.service.log.LogEntry;
+
+
+public enum Priority {
+
+ Error(LOG_ERROR),
+ Warning(LOG_WARNING),
+ Info(LOG_INFO),
+ Debug(LOG_DEBUG),
+
+ Any(5);
+
+ private final int level;
+
+ private Priority(int priority) {
+ this.level = priority;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public static Priority valueOf(LogEntry entry) {
+ for (Priority priority : values()) {
+ if (priority.level == entry.getLevel()) {
+ return priority;
+ }
+ }
+
+ return Any;
+ }
+
+}
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/BundleMatcher.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/BundleMatcher.java
new file mode 100644
index 0000000..a872d99
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/BundleMatcher.java
@@ -0,0 +1,15 @@
+package org.apache.karaf.webconsole.osgi.internal.log.search;
+
+import org.apache.karaf.webconsole.osgi.internal.log.Options;
+import org.osgi.service.log.LogEntry;
+
+public class BundleMatcher implements Matcher {
+
+ public boolean matches(LogEntry entry, Options options) {
+ if (options.getBundleNameFragment() != null && !options.getBundleNameFragment().isEmpty()) {
+ return entry.getBundle().getSymbolicName().contains(options.getBundleNameFragment());
+ }
+ return true;
+ }
+
+}
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/DateFromMatcher.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/DateFromMatcher.java
new file mode 100644
index 0000000..0fe414c
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/DateFromMatcher.java
@@ -0,0 +1,15 @@
+package org.apache.karaf.webconsole.osgi.internal.log.search;
+
+import org.apache.karaf.webconsole.osgi.internal.log.Options;
+import org.osgi.service.log.LogEntry;
+
+public class DateFromMatcher implements Matcher {
+
+ public boolean matches(LogEntry entry, Options options) {
+ if (options.getDateFrom() != null && options.getDateFrom() > 0) {
+ return entry.getTime() >= options.getDateFrom();
+ }
+ return true;
+ }
+
+}
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/DateToMatcher.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/DateToMatcher.java
new file mode 100644
index 0000000..4a37e65
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/DateToMatcher.java
@@ -0,0 +1,15 @@
+package org.apache.karaf.webconsole.osgi.internal.log.search;
+
+import org.apache.karaf.webconsole.osgi.internal.log.Options;
+import org.osgi.service.log.LogEntry;
+
+public class DateToMatcher implements Matcher {
+
+ public boolean matches(LogEntry entry, Options options) {
+ if (options.getDateTo() != null && options.getDateTo() > 0) {
+ return entry.getTime() <= options.getDateTo();
+ }
+ return true;
+ }
+
+}
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/Matcher.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/Matcher.java
new file mode 100644
index 0000000..26c08a3
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/Matcher.java
@@ -0,0 +1,10 @@
+package org.apache.karaf.webconsole.osgi.internal.log.search;
+
+import org.apache.karaf.webconsole.osgi.internal.log.Options;
+import org.osgi.service.log.LogEntry;
+
+public interface Matcher {
+
+ boolean matches(LogEntry entry, Options options);
+
+}
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/MessageMatcher.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/MessageMatcher.java
new file mode 100644
index 0000000..07e0734
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/MessageMatcher.java
@@ -0,0 +1,15 @@
+package org.apache.karaf.webconsole.osgi.internal.log.search;
+
+import org.apache.karaf.webconsole.osgi.internal.log.Options;
+import org.osgi.service.log.LogEntry;
+
+public class MessageMatcher implements Matcher {
+
+ public boolean matches(LogEntry entry, Options options) {
+ if (options.getMessageFragment() != null && !options.getMessageFragment().isEmpty()) {
+ return entry.getMessage().contains(options.getMessageFragment());
+ }
+ return true;
+ }
+
+}
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/PriorityMatcher.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/PriorityMatcher.java
new file mode 100644
index 0000000..fe1af86
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/log/search/PriorityMatcher.java
@@ -0,0 +1,12 @@
+package org.apache.karaf.webconsole.osgi.internal.log.search;
+
+import org.apache.karaf.webconsole.osgi.internal.log.Options;
+import org.osgi.service.log.LogEntry;
+
+public class PriorityMatcher implements Matcher {
+
+ public boolean matches(LogEntry entry, Options options) {
+ return options.getPriority().getLevel() >= entry.getLevel();
+ }
+
+}
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/sidebar/OsgiSidebar.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/sidebar/OsgiSidebar.java
index b2d1b9f..0804da2 100644
--- a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/sidebar/OsgiSidebar.java
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/sidebar/OsgiSidebar.java
@@ -27,6 +27,7 @@
import org.apache.karaf.webconsole.osgi.internal.bundle.BundlesPage;
import org.apache.karaf.webconsole.osgi.internal.configuration.ConfigurationsPage;
import org.apache.karaf.webconsole.osgi.internal.event.EventsPage;
+import org.apache.karaf.webconsole.osgi.internal.log.LogsPage;
import org.apache.wicket.Page;
import org.apache.wicket.markup.html.link.Link;
@@ -39,7 +40,8 @@
public List<Link<Page>> getItems(String componentId, String labelId) {
return Arrays.asList(
createPageLink(componentId, labelId, "Configuration", ConfigurationsPage.class),
- createPageLink(componentId, labelId, "Events", EventsPage.class)
+ createPageLink(componentId, labelId, "Events", EventsPage.class),
+ createPageLink(componentId, labelId, "Log entries", LogsPage.class)
);
}
diff --git a/osgi/src/main/resources/OSGI-INF/blueprint/osgi.xml b/osgi/src/main/resources/OSGI-INF/blueprint/osgi.xml
index 28837a7..5608796 100644
--- a/osgi/src/main/resources/OSGI-INF/blueprint/osgi.xml
+++ b/osgi/src/main/resources/OSGI-INF/blueprint/osgi.xml
@@ -50,6 +50,7 @@
<reference id="startLevel" interface="org.osgi.service.startlevel.StartLevel" />
<reference id="packageAdmin" interface="org.osgi.service.packageadmin.PackageAdmin" />
<reference id="metaTypeService" interface="org.osgi.service.metatype.MetaTypeService" />
+ <reference id="logReader" interface="org.osgi.service.log.LogReaderService" />
<reference-list id="columnProviders" interface="org.apache.karaf.webconsole.osgi.bundle.IColumnProvider" availability="optional" />
<reference-list id="actionProviders" interface="org.apache.karaf.webconsole.osgi.bundle.IActionProvider" availability="optional" />
diff --git a/osgi/src/main/resources/org/apache/karaf/webconsole/osgi/internal/log/LogsPage.html b/osgi/src/main/resources/org/apache/karaf/webconsole/osgi/internal/log/LogsPage.html
new file mode 100644
index 0000000..eef6050
--- /dev/null
+++ b/osgi/src/main/resources/org/apache/karaf/webconsole/osgi/internal/log/LogsPage.html
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd" >
+ <wicket:head>
+ <title>Karaf WebConsole</title>
+ </wicket:head>
+
+ <body>
+ <wicket:extend>
+ <h1>Log entries</h1>
+
+ <div class="grid_9">
+ <form wicket:id="filters">
+
+ <label for="priority">
+ Priority
+ <select wicket:id="priority" id="priority">
+ <wicket:container wicket:id="options">
+ <option wicket:id="option">label</option>
+ </wicket:container>
+ </select>
+ </label>
+
+ <label for="dateFrom">
+ Date from
+ <input type="text" id="dateFrom" wicket:id="dateFrom" />
+ </label>
+
+ <label for="dateTo">
+ Date to
+ <input type="text" id="dateTo" wicket:id="dateTo" />
+ </label>
+
+ <label for="messageFragment">
+ Message
+ <input type="text" id="messageFragment" wicket:id="messageFragment" />
+ </label>
+
+ <label for="bundleNameFragment">
+ Bundle
+ <input type="text" id="bundleNameFragment" wicket:id="bundleNameFragment" />
+ </label>
+
+ <input wicket:id="submit" type="submit" value="Filter" />
+ </form>
+ </div>
+
+ <table wicket:id="logs" class="dataview" />
+
+ </wicket:extend>
+ </body>
+</html>