Using the new SciJava logging and status service system in an application

logging
scijava
gui
Tags: #<Tag:0x00007fb882f864b0> #<Tag:0x00007fb882f860c8> #<Tag:0x00007fb882f854c0>

#1

Hi all.

We are currently building a new application based on SciJava. It consists of a GUI that controls a class that does something. Formally:

  • I have a class A (says, TrackMate) that can generate log messages (like “Computing finished in…”).
  • I have a class B (says, a GUI that controls TrackMate) in which I would like to display these messages. B should only show the messages that were generated from A, so I am trying to find a way to filter it so.

I understand that A must implement or have a Logger. But what of B?

Doing this would be beneficial, because

  • A would reuse SciJava framework,
  • and would not have to know about B. Which is fine if it is ever reused somewhere else (command line, …)

#2

Hi @tinevez, It’s obviously my turn to answer this question, as I recently rewrote the scijava LogService.
I have set up an example for you: https://github.com/maarzt/scijava-log-examples

Basically it works like this:

  • Both class A and class B should have a @Parameter of type Logger.
  • A Logger can have a sub logger (Use Logger.subLogger to create it). A sub logger forwards all the log messages it get’s to the parent logger.
  • Before class B executes class A, it should create a sub logger, and give this sub logger as a parameter to class A. That way all log messages of class A are forwarded to the logger of class B.
  • To display the log messages you may use the class LoggingPanel, which is part of the maven package scijava-ui-swing. LoggingPanel is a swing component, that display’s log messages. Use the method Logger.addLogListener to add the LoggingPanel as a listener to the logger of class B.

#3

Thanks! I will go an play with it.
But quickly:

  1. Is there a way to make a custom logging panel that does not inherit from the LoggingPanel in scijava-swing?
  2. Does this panel collects ALL log messages or just the ones from emitters he has been add as a listener to?

#4

Yes, of cause. The LoggingPanel implements the LogListener interface. It’s possible to simply implement a custom LogListener and display the log messages anyway you want.
But the LoggingPanel is intended for exactly the situation you described above. If you wouldn’t want to use, please tell me why, and maybe we can improve it, such that it’s applicable to your usecase.

If you add the LoggingPanel as listener to a Logger it does NOT get all messages. It only get’s the messages that are logged to that particular logger and it’s sub loggers.
If you want to get all log messages, add the LoggingPanel or your custom LogListener as listener to the LogService.


#6

I tried to adapt your example (thank you!) but I have big issues.

My ‘mother’ class is @SciJavaPlugin and not a Command.
In my case, the @Parameter Logger is always null, despite other @Parameters (such as OpService) are not. I nonetheless instantiate this plugin the SciJava way.

Where should I look to fix this?


#7

Did you try to Context.inject(...) the mother class?


#8

I can’t; the mother class is a MastodonPlugin.
Let me go through the bottom of this.


#9

It’s null even after injection.


#10

There are really things I do not understand with the Logger thingie.
I was able to run @maarzt examples from my project, but trying to adapt it to something else always failed.

For instance if I put it as a @Parameter in a Op, like this:

@Plugin( type = SemiAutomaticTracker.class )
public class SemiAutomaticTracker
		extends AbstractBinaryComputerOp< Collection< Spot >, Map< String, Object >, Model >
		implements HasErrorMessage, Cancelable
{

	@Parameter
	private Logger log;

	@Parameter
	private ThreadService threadService;

the op will not even be matched properly. The matcher expects the concrete logger instance to be provided. The SciJava mechanisms do not inject it. For instance, trying to call the above op with this:

final SemiAutomaticTracker tracker = ( SemiAutomaticTracker ) Computers.binary(
					ops, SemiAutomaticTracker.class, model, spots, settings,
					spimData );

results in matching error:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: No matching 'org.mastodon.trackmate.semiauto.SemiAutomaticTracker/net.imagej.ops.special.computer.BinaryComputerOp' op

Request:
-	org.mastodon.trackmate.semiauto.SemiAutomaticTracker/net.imagej.ops.special.computer.BinaryComputerOp(
		Model,
		RefSetImp,
		HashMap,
		SpimDataMinimal)

Candidates:
1. 	(Model out,
		String errorMessage,
		boolean ok) =
	org.mastodon.trackmate.semiauto.SemiAutomaticTracker(
		Model out,
		Collection in1,
		Map in2,
		Logger log,
		SpimDataMinimal spimData)
	Not enough arguments: 4 < 5

as if the Logger is not a SciJava thing.

This is normal: it is not a SciJava service. But then I don’t understand why it works at all for @maarzt.


#11

Parameters annotated with @Parameter are processed by PreprocessorPlugins such as ServicePreprocessor (for Services) and LoggerPreprocessor (for Logger). At least that’s how it should work for SciJava Command plugins, but I’m not sure about how that exactly works for Ops.

The scijava-log-examples work fine because the Logger is explicitly given when calling the plugin here:

Can you point to your source code? I guess that would make debugging a little easier…


#12

Unfortunately no. It’s still a battlefield and I do not commit.


#13

How about putting it on a topic branch battlefield? :wink:


#14

@tinevez You found an important problem:

The Logger get’s filled in correctly ONLY if you use the CommandService to run the command. But Context.inject() and ops matching don’t treat the Logger correctly. Sorry, didn’t see that problem coming, and I don’t think this can be fixed easily. I would need to find a solution together with @ctrueden

A workaround would look like this:

  1. Set required = false for Logger parameter
  2. Add LogService as paramter
  3. If Logger is null use LogService instead.
    This could look like: if(logger == null) logger = logService;

#15

Thanks! Thanks! Thanks!


#16

@maarzt is right that we probably need to make Logger injection work for non-module plugins.

@tinevez For now, I suggest just sticking to @Parameter LogService log.

We need to make a decision about whether @Parameter injection needs to be extensible for non-module plugins. If not, we can hardcode the logger injection. Right now, inject always injects Service and Context parameters, and also registers @EventHandler methods. But the injection logic could be extended to handle Logger parameters as well. We already special-case the event service—why not the log service, too?

I am in the midst of a modularization of the core SciJava packages, which is helping to clean up some issues like this. But I don’t have an answer yet for how best to address this. My instinct is to make it extensible, so that the EventService and LogService do not get special hardcoded treatment, and so that others can add their own subsystems with their own injected parameter types. But it needs to be done in a way that keeps context injection fast.


#17

Hi @maarzt

The workaround works fine thanks!

Could you tell me how I could programatically set the settings for message formatter? It seems that they are not persisted.


#18

Hi @tinevez,

You can make the settings for the message formatter persistent by providing PrefService and a key to the LoggingPanel constructor, see:

Currently it’s not possible to programatically set the settings for the message formatter. But I can change that if needed.


#19

Actually, this is what I use, and the settings are not persisted.


#20

It only works if the key starts with a slash.
For example "my_key_for_persistence" fails, but "/my_key_for_persistence" works.
This strange behavior is not intended but caused by PrefService.


#21

That’s surprising. Is this documented in form of an issue somewhere? I didn’t find any.