]> gerrit.simantics Code Review - simantics/platform.git/commitdiff
Add UI dialog for dynamic reconfiguration of Logback loggers
authorTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Thu, 21 Jan 2021 22:05:06 +0000 (00:05 +0200)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Mon, 25 Jan 2021 09:40:35 +0000 (11:40 +0200)
gitlab #670

Change-Id: Icf4af49e7320108938075c27388b0616f5dd4a14

bundles/org.simantics.logging.ui/META-INF/MANIFEST.MF
bundles/org.simantics.logging.ui/fragment.e4xmi
bundles/org.simantics.logging.ui/src/org/simantics/logging/ui/Activator.java
bundles/org.simantics.logging.ui/src/org/simantics/logging/ui/LoggerManagementDialog.java [new file with mode: 0644]
bundles/org.simantics.logging.ui/src/org/simantics/logging/ui/handlers/ConfigureLoggingHandler.java [new file with mode: 0644]
bundles/org.simantics.logging/src/org/simantics/logging/LogConfigurator.java
bundles/org.simantics.logging/src/org/simantics/logging/LoggerLevel.java [new file with mode: 0644]

index 6002850180f3ad3ceec138bdafe1033302d968cc..f2617b78d7c177f9767d2988ae913d2e3d7a2a75 100644 (file)
@@ -10,6 +10,7 @@ Require-Bundle: javax.inject,
  org.eclipse.e4.ui.services,
  org.eclipse.e4.core.di.annotations,
  org.eclipse.core.runtime,
+ org.eclipse.ui,
  org.slf4j.api,
  org.simantics.logging,
  org.simantics.utils.ui
index cc14bb5f84c23a1824dd5c8854a5eaf9597f0bd6..fa7deef3c1670d31edf392f94741eb2dafac0daf 100644 (file)
@@ -5,10 +5,12 @@
     <elements xsi:type="commands:Command" xmi:id="_34UCQB3xEeiTyNdCNKIG-w" elementId="org.simantics.logging.ui.command.selectLogLevel" commandName="Select Logging Level" description="Select current logging level">
       <parameters xmi:id="_YFD2kB3yEeiTyNdCNKIG-w" elementId="org.simantics.logging.ui.commandparameter.selectLoggingLevel" name="Logging Level" optional="false"/>
     </elements>
+    <elements xsi:type="commands:Command" xmi:id="_ZkidYFvnEeuYeLW_dhcHUw" elementId="org.simantics.logging.ui.configure" commandName="Configure Logging..." description="Configure logging levels per package"/>
   </fragments>
   <fragments xsi:type="fragment:StringModelFragment" xmi:id="_fW12kIrOEeW7h_qdP9N9fw" featurename="handlers" parentElementId="xpath:/">
     <elements xsi:type="commands:Handler" xmi:id="_k2L0IIrOEeW7h_qdP9N9fw" elementId="org.simantics.logging.ui.handlers.saveLogFiles" contributionURI="bundleclass://org.simantics.logging.ui/org.simantics.logging.ui.handlers.SaveLogFilesHandler" command="_UCYfwIrOEeW7h_qdP9N9fw"/>
     <elements xsi:type="commands:Handler" xmi:id="_60CMgB3xEeiTyNdCNKIG-w" elementId="org.simantics.logging.ui.handlers.selectLogLevel" contributionURI="bundleclass://org.simantics.logging.ui/org.simantics.logging.ui.handlers.SelectLoggingLevelHandler" command="_34UCQB3xEeiTyNdCNKIG-w"/>
+    <elements xsi:type="commands:Handler" xmi:id="_pVB6oFvnEeuYeLW_dhcHUw" elementId="org.simantics.logging.ui.handlers.configureLogging" contributionURI="bundleclass://org.simantics.logging.ui/org.simantics.logging.ui.handlers.ConfigureLoggingHandler" command="_ZkidYFvnEeuYeLW_dhcHUw"/>
   </fragments>
   <fragments xsi:type="fragment:StringModelFragment" xmi:id="_pVgfIIrOEeW7h_qdP9N9fw" featurename="menuContributions" parentElementId="xpath:/">
     <elements xsi:type="menu:MenuContribution" xmi:id="_tSwX0IrOEeW7h_qdP9N9fw" elementId="org.simantics.logging.ui.menucontribution.saveLogFiles" positionInParent="after=group.main.ext" parentId="help">
@@ -31,6 +33,7 @@
         <children xsi:type="menu:HandledMenuItem" xmi:id="_55I1EB3yEeiTyNdCNKIG-w" elementId="org.simantics.logging.ui.handledmenuitem.selectLoggingLevel.error" label="ERROR" iconURI="platform:/plugin/org.simantics.logging.ui/icons/error.png" type="Radio" command="_34UCQB3xEeiTyNdCNKIG-w">
           <parameters xmi:id="_55I1ER3yEeiTyNdCNKIG-w" elementId="org.simantics.logging.ui.parameter.loggingLevel.error" name="org.simantics.logging.ui.commandparameter.selectLoggingLevel" value="ERROR"/>
         </children>
+        <children xsi:type="menu:HandledMenuItem" xmi:id="_ZOqzsFvqEeuYeLW_dhcHUw" elementId="org.simantics.logging.ui.handledmenuitem.configureLogging" label="Configure Logging..." tooltip="Configure logging levels per package" command="_ZkidYFvnEeuYeLW_dhcHUw"/>
       </children>
     </elements>
   </fragments>
index 942a88ba2c030c65399d088a8188047bb6dc2b18..34bc2a659110c83fa5a37e38fb2eeeaae5ae323b 100644 (file)
@@ -1,30 +1,50 @@
 package org.simantics.logging.ui;
 
-import org.osgi.framework.BundleActivator;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
 import org.osgi.framework.BundleContext;
 
-public class Activator implements BundleActivator {
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class Activator extends AbstractUIPlugin {
 
-       private static BundleContext context;
+       // The plug-in ID
+       public static final String PLUGIN_ID = "org.simantics.logging.ui"; //$NON-NLS-1$
 
-       static BundleContext getContext() {
-               return context;
+       // The shared instance
+       private static Activator plugin;
+
+       /**
+        * The constructor
+        */
+       public Activator() {
        }
 
        /*
         * (non-Javadoc)
-        * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+        * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
         */
-       public void start(BundleContext bundleContext) throws Exception {
-               Activator.context = bundleContext;
+       public void start(BundleContext context) throws Exception {
+               super.start(context);
+               plugin = this;
        }
 
        /*
         * (non-Javadoc)
-        * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+        * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+        */
+       public void stop(BundleContext context) throws Exception {
+               plugin = null;
+               super.stop(context);
+       }
+
+       /**
+        * Returns the shared instance
+        *
+        * @return the shared instance
         */
-       public void stop(BundleContext bundleContext) throws Exception {
-               Activator.context = null;
+       public static Activator getDefault() {
+               return plugin;
        }
 
 }
diff --git a/bundles/org.simantics.logging.ui/src/org/simantics/logging/ui/LoggerManagementDialog.java b/bundles/org.simantics.logging.ui/src/org/simantics/logging/ui/LoggerManagementDialog.java
new file mode 100644 (file)
index 0000000..7ef5cc6
--- /dev/null
@@ -0,0 +1,349 @@
+package org.simantics.logging.ui;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.dialogs.TrayDialog;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.jface.layout.TableColumnLayout;
+import org.eclipse.jface.resource.FontDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.ComboBoxCellEditor;
+import org.eclipse.jface.viewers.EditingSupport;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.TextCellEditor;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerCell;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CCombo;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.simantics.logging.LogConfigurator;
+import org.simantics.logging.LoggerLevel;
+
+public class LoggerManagementDialog extends TrayDialog {
+
+       private static final String DIALOG = LoggerManagementDialog.class.getSimpleName(); //$NON-NLS-1$
+
+       private static final String[] LEVELS = {
+                       "ERROR",
+                       "WARN",
+                       "INFO",
+                       "DEBUG",
+                       "TRACE",
+       };
+
+       private static final Map<String, Integer> LEVEL_TO_INDEX = new HashMap<>();
+
+       static {
+               for (int i = 0; i < LEVELS.length; i++) {
+                       LEVEL_TO_INDEX.put(LEVELS[i], i);
+               }
+       }
+
+       /**
+        * ID for Apply buttons.
+        */
+       private int APPLY_ID = IDialogConstants.CLIENT_ID + 1;
+
+       /**
+        * The label for Apply buttons.
+        */
+       private String APPLY_LABEL = JFaceResources.getString("apply"); //$NON-NLS-1$
+
+       private TableViewer tableViewer;
+       private List<LoggerLevel> configuration;
+       private IDialogSettings dialogBoundsSettings;
+
+       private Consumer<List<LoggerLevel>> applyFunction;
+
+       public LoggerManagementDialog(Shell shell, Consumer<List<LoggerLevel>> applyFunction) {
+               super(shell);
+               reloadConfiguration();
+               this.applyFunction = applyFunction;
+
+               IDialogSettings settings = Activator.getDefault().getDialogSettings();
+               dialogBoundsSettings = settings.getSection(DIALOG);
+               if (dialogBoundsSettings == null)
+                       dialogBoundsSettings = settings.addNewSection(DIALOG);
+       }
+
+       private void reloadConfiguration() {
+               this.configuration = LogConfigurator.listConfiguredLoggers();
+               addEmptyRow();
+       }
+
+       private void addEmptyRow() {
+               this.configuration.add(new LoggerLevel("", LEVELS[2]));
+       }
+
+       @Override
+       protected IDialogSettings getDialogBoundsSettings() {
+               return dialogBoundsSettings;
+       }
+
+       @Override
+       protected void configureShell(Shell newShell) {
+               super.configureShell(newShell);
+               newShell.setText("Manage Loggers");
+               newShell.setMinimumSize(800, 600);
+       }
+
+       @Override
+       protected boolean isResizable() {
+               return true;
+       }
+
+       @Override
+       protected void createButtonsForButtonBar(Composite parent) {
+               createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
+               createButton(parent, APPLY_ID, APPLY_LABEL, false);
+               createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
+       }
+
+       @Override
+       protected void buttonPressed(int buttonId) {
+               if (buttonId == IDialogConstants.CLOSE_ID) {
+                       super.buttonPressed(IDialogConstants.CANCEL_ID);
+               } else if (buttonId == APPLY_ID) {
+                       if (applyFunction != null) {
+                               applyFunction.accept(configuration);
+                               reloadConfiguration();
+                               setInput();
+                       }
+               } else {
+                       super.buttonPressed(buttonId);
+               }
+       }
+
+       @Override
+       protected Control createDialogArea(Composite parent) {
+               final Composite composite = (Composite) super.createDialogArea(parent);
+               GridLayoutFactory.fillDefaults().margins(10, 10).numColumns(1).applyTo(composite);
+               GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
+
+               Composite tableComposite = new Composite(composite, SWT.NONE);
+               TableColumnLayout tcl = new TableColumnLayout();
+               tableComposite.setLayout(tcl);
+               GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite);
+
+               tableViewer = new TableViewer(tableComposite, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
+
+               Display display = getShell().getDisplay(); 
+
+               // Fonts
+               final Font systemFont = display.getSystemFont();
+               final Font italic = FontDescriptor.createFrom(systemFont).setStyle(SWT.ITALIC).createFont(display);
+               final Font bold = FontDescriptor.createFrom(systemFont).setStyle(SWT.BOLD).createFont(display);
+               tableViewer.getTable().addDisposeListener(e -> {
+                       italic.dispose();
+                       bold.dispose();
+               });
+
+               Function<LoggerLevel, Font> fontFunction = l -> { 
+                       if (l.isLoggerDefined()) {
+                               if (l.levelChanged()) {
+                                       return bold;
+                               }
+                               return systemFont;
+                       }
+                       return italic;
+               };
+
+               // Column 1: logger name
+               TableViewerColumn column1 = new TableViewerColumn(tableViewer, SWT.NONE);
+               column1.getColumn().setWidth(300);
+               column1.getColumn().setText("Logger");
+               column1.getColumn().setToolTipText("Package Name of Logger");
+               column1.setLabelProvider(new ColumnLabelProvider() {
+                       @Override
+                       public void update(ViewerCell cell) {
+                               LoggerLevel l = (LoggerLevel) cell.getElement();
+                               cell.setText(l.getName());
+                               cell.setFont(fontFunction.apply(l));
+                       }
+               });
+               column1.setEditingSupport(new EditingSupport(tableViewer) {
+                       CellEditor editor = new TextCellEditor(tableViewer.getTable());
+                       @Override
+                       protected CellEditor getCellEditor(Object element) {
+                               return editor;
+                       }
+                       @Override
+                       protected boolean canEdit(Object element) {
+                               LoggerLevel l = (LoggerLevel) element;
+                               return !l.isLoggerDefined();
+                       }
+                       @Override
+                       protected Object getValue(Object element) {
+                               return ((LoggerLevel)element).getName();
+                       }
+                       @Override
+                       protected void setValue(Object element, Object value) {
+                               LoggerLevel l = (LoggerLevel) element;
+                               String s = ((String) value).trim();
+
+                               boolean sameNameExists = !s.isEmpty() && configuration.stream().anyMatch(ll -> ll != l && ll.getName().equals(s));
+
+                               // Prevent providing the same package twice
+                               if (sameNameExists)
+                                       return;
+
+                               String previousName = l.getName();
+                               l.setName(s);
+                               if (s.isEmpty()) {
+                                       int index = configuration.indexOf(l);
+                                       if (index >= 0 && index < (configuration.size()-1)) {
+                                               configuration.remove(index);
+                                       }
+                               } else if (previousName.isEmpty()) {
+                                       addEmptyRow();
+                               }
+                               setInput();
+                       }
+               });
+
+               // Column 2: logging level of logger
+               TableViewerColumn column2 = new TableViewerColumn(tableViewer, SWT.NONE);
+               column2.getColumn().setWidth(100);
+               column2.getColumn().setText("Log Level");
+               column2.getColumn().setToolTipText("Logging Level for Package and Subpackages");
+               column2.setLabelProvider(new ColumnLabelProvider() {
+                       @Override
+                       public void update(ViewerCell cell) {
+                               LoggerLevel l = (LoggerLevel) cell.getElement();
+                               cell.setText(l.getLevel());
+                               cell.setFont(fontFunction.apply(l));
+                       }
+               });
+               column2.setEditingSupport(new EditingSupport(tableViewer) {
+                       @Override
+                       protected boolean canEdit(Object element) {
+                               return true;
+                       }
+                       @Override
+                       protected CellEditor getCellEditor(Object element) {
+                               return new ComboBoxCellEditor(tableViewer.getTable(), LEVELS, SWT.READ_ONLY) {
+                                       @Override
+                                       protected Control createControl(Composite parent) {
+                                               CCombo combo = (CCombo) super.createControl(parent);
+                                               // The only way found to actually open the combo box list
+                                               // right away when starting to edit.
+                                               combo.getDisplay().asyncExec(() -> {
+                                                       if (!combo.isDisposed())
+                                                               combo.setListVisible(true);
+                                               });
+                                               return combo;
+                                       }
+                               };
+                       }
+                       @Override
+                       protected Object getValue(Object element) {
+                               LoggerLevel l = (LoggerLevel) element;
+                               return LEVEL_TO_INDEX.get(l.getLevel());
+                       }
+                       @Override
+                       protected void setValue(Object element, Object value) {
+                               LoggerLevel l = (LoggerLevel)element;
+                               l.setLevel(LEVELS[(Integer) value]);
+                               getViewer().update(element, null);
+                       }
+               });
+
+               tcl.setColumnData(column1.getColumn(), new ColumnWeightData(5, 300));
+               tcl.setColumnData(column2.getColumn(), new ColumnWeightData(1, 150));
+
+               // Decorations
+               tableViewer.getTable().setHeaderVisible(true);
+               tableViewer.getTable().setLinesVisible(true);
+
+               // Table content
+               tableViewer.setContentProvider(ArrayContentProvider.getInstance());
+
+               tableViewer.getTable().addListener(SWT.KeyDown, e -> {
+                       if (e.keyCode == SWT.DEL) {
+                               @SuppressWarnings("unchecked")
+                               List<Object> s = tableViewer.getStructuredSelection().toList();
+                               Predicate<Object> removable = l -> {
+                                       LoggerLevel ll = (LoggerLevel) l;
+                                       return ll.isLoggerDefined() && !ll.getName().isEmpty();
+                               };
+                               if (s.stream().allMatch(removable)) {
+                                       s.forEach(configuration::remove);
+                                       setInput();
+                               }
+                       }
+               });
+
+               Text filterText = new Text(composite, SWT.FLAT | SWT.BORDER);
+               GridDataFactory.fillDefaults().grab(true, false).applyTo(filterText);
+               filterText.setToolTipText("Package Name Filter");
+               filterText.moveAbove(tableComposite);
+               filterText.addModifyListener(e -> {
+                       String filter = filterText.getText().trim().toLowerCase();
+                       ViewerFilter[] filters = {};
+                       if (!filter.isEmpty()) {
+                               filters = new ViewerFilter[] {
+                                               new ViewerFilter() {
+                                                       @Override
+                                                       public boolean select(Viewer viewer, Object parentElement, Object element) {
+                                                               LoggerLevel l = (LoggerLevel) element;
+                                                               return !l.isLoggerDefined() || l.levelChanged()
+                                                                               ? true
+                                                                               : l.getName().toLowerCase().contains(filter);
+                                                       }
+                                               }
+                               };
+                       };
+                       scheduleSetFilters(filters);
+               });
+
+               setInput();
+
+               return composite;
+       }
+
+       private void setInput() {
+               tableViewer.setInput(configuration.toArray(new LoggerLevel[configuration.size()]));
+       }
+
+       public List<LoggerLevel> getConfiguration() {
+               return configuration;
+       }
+
+       private final AtomicReference<ViewerFilter[]> filtersToSet = new AtomicReference<>();
+       private final Runnable setFilters = () -> {
+               if (!tableViewer.getTable().isDisposed()) {
+                       ViewerFilter[] fs = filtersToSet.getAndSet(null);
+                       if (fs != null) {
+                               tableViewer.getTable().setRedraw(false);
+                               tableViewer.setFilters(fs);
+                               tableViewer.getTable().setRedraw(true);
+                       }
+               }
+       };
+
+       protected void scheduleSetFilters(ViewerFilter[] array) {
+               filtersToSet.set(array);
+               getShell().getDisplay().timerExec(250, setFilters);
+       }
+
+}
diff --git a/bundles/org.simantics.logging.ui/src/org/simantics/logging/ui/handlers/ConfigureLoggingHandler.java b/bundles/org.simantics.logging.ui/src/org/simantics/logging/ui/handlers/ConfigureLoggingHandler.java
new file mode 100644 (file)
index 0000000..8518a43
--- /dev/null
@@ -0,0 +1,33 @@
+package org.simantics.logging.ui.handlers;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.inject.Named;
+
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.services.IServiceConstants;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Shell;
+import org.simantics.logging.LogConfigurator;
+import org.simantics.logging.LoggerLevel;
+import org.simantics.logging.ui.LoggerManagementDialog;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 1.47.0
+ */
+public class ConfigureLoggingHandler {
+
+       @Execute
+       public void execute(@Named(IServiceConstants.ACTIVE_SHELL) Shell shell) {
+               Consumer<List<LoggerLevel>> applyConfiguration = LogConfigurator::applyLogLevels;
+
+               LoggerManagementDialog dialog = new LoggerManagementDialog(shell, applyConfiguration);
+               if (dialog.open() == Window.OK) {
+                       applyConfiguration.accept(dialog.getConfiguration());
+               }
+       }
+
+}
\ No newline at end of file
index 047bac8b61cd03f6b18b8317929cebaa0c3a3753..2928de681e27dc901796fa5ead9880444d038cb7 100644 (file)
@@ -1,6 +1,8 @@
 package org.simantics.logging;
 
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -42,13 +44,53 @@ public final class LogConfigurator {
             LOGGER.info("Setting logger level to {} for loggers {}", level, logger);
         LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
         Level ll = getLoggerLevel(level);
-        ch.qos.logback.classic.Logger loggerList = context.getLogger(logger);
-        loggerList.setLevel(ll);
+        ch.qos.logback.classic.Logger l = context.getLogger(logger);
+        l.setLevel(ll);
         if (LOGGER.isDebugEnabled())
-            LOGGER.debug("Loggers installed {}", loggerList);
+            LOGGER.debug("Logger {} level set to {}", l, ll);
     }
 
     private static Level getLoggerLevel(String level) {
         return Level.valueOf(level);
     }
+
+    /**
+     * @since 1.47.0
+     */
+    public static List<LoggerLevel> listConfiguredLoggers() {
+        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+        return context.getLoggerList().stream()
+                .map(l -> {
+                    //LOGGER.info("{} : {} : {} : {}", l, l.getName(), l.getLevel(), l.getEffectiveLevel());
+                    if (l.getName().isEmpty())
+                        return null;
+                    return new LoggerLevel(l, l.getEffectiveLevel().toString());
+                })
+                .filter(Objects::nonNull)
+                .sorted()
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * @since 1.47.0
+     */
+    public static void applyLogLevels(List<LoggerLevel> loggers) {
+        loggers.forEach(l -> {
+            Level level = getLoggerLevel(l.getLevel());
+            Logger logger = l.getLogger();
+            if (logger instanceof ch.qos.logback.classic.Logger) {
+                LOGGER.info("Setting existing logger {} level to {}", l.getName(), l.getLevel());
+                ch.qos.logback.classic.Logger ll = (ch.qos.logback.classic.Logger) l.getLogger();
+                ll.setLevel(level);
+            } else if (!l.getName().trim().isEmpty()) {
+                LOGGER.info("Defining new logger {} with level {}", l.getName(), l.getLevel());
+                logger = LoggerFactory.getLogger(l.getName());
+                if (logger instanceof ch.qos.logback.classic.Logger) {
+                    ch.qos.logback.classic.Logger ll = (ch.qos.logback.classic.Logger) logger;
+                    ll.setLevel(level);
+                }
+            }
+        });
+    }
+
 }
diff --git a/bundles/org.simantics.logging/src/org/simantics/logging/LoggerLevel.java b/bundles/org.simantics.logging/src/org/simantics/logging/LoggerLevel.java
new file mode 100644 (file)
index 0000000..0beeb26
--- /dev/null
@@ -0,0 +1,94 @@
+package org.simantics.logging;
+
+import java.util.Objects;
+
+import org.slf4j.Logger;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 1.47.0
+ */
+public class LoggerLevel implements Comparable<LoggerLevel> {
+
+       private final Logger logger;
+       private final String originalLevel;
+       private String loggerName;
+       private String level;
+
+       public LoggerLevel(Logger logger, String level) {
+               Objects.requireNonNull(logger, "logger");
+               Objects.requireNonNull(level, "level");
+               this.logger = logger;
+               this.originalLevel = level;
+               this.loggerName = logger.getName();
+               this.level = level;
+       }
+
+       public LoggerLevel(String loggerName, String level) {
+               Objects.requireNonNull(loggerName, "loggerName");
+               Objects.requireNonNull(level, "level");
+               this.logger = null;
+               this.originalLevel = null;
+               this.loggerName = loggerName;
+               this.level = level;
+       }
+
+       Logger getLogger() {
+               return logger;
+       }
+
+       public String getName() {
+               return loggerName;
+       }
+
+       public LoggerLevel setName(String name) {
+               if (logger != null)
+                       throw new UnsupportedOperationException("Cannot rename already created logger " + loggerName + " to " + name);
+               Objects.requireNonNull(name, "name");
+               this.loggerName = name;
+               return this;
+       }
+
+       public String getLevel() {
+               return level;
+       }
+
+       public LoggerLevel setLevel(String level) {
+               Objects.requireNonNull(level, "level");
+               this.level = level;
+               return this;
+       }
+
+       public boolean levelChanged() {
+               return !Objects.equals(originalLevel, level);
+       }
+
+       public boolean isLoggerDefined() {
+               return logger != null;
+       }
+
+       @Override
+       public int hashCode() {
+               return Objects.hash(level, logger, loggerName);
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (this == obj)
+                       return true;
+               if (obj == null)
+                       return false;
+               if (getClass() != obj.getClass())
+                       return false;
+               LoggerLevel other = (LoggerLevel) obj;
+               return Objects.equals(level, other.level)
+                               && Objects.equals(logger, other.logger)
+                               && Objects.equals(loggerName, other.loggerName);
+       }
+
+       @Override
+       public int compareTo(LoggerLevel o) {
+               return loggerName.compareToIgnoreCase(o.loggerName);
+       }
+
+}