As described in Can't call one closure from another, I am using a pluggable script from within a Grails app.
Unfortunately, I've found that I can't use log4j from within these scripts. I am forced to use println.
I tried using
import org.apache.commons.logging.LogFactory
def Log log = LogFactory.getLog(getClass())
but I got no output. When I print out the result of the call to getClass(), I get something like
myscript$_run_closure5
So I'm thinking the issue is that there is no configuration in my Grails Config.groovy file for this class.
Is there a way for me to programmatically add these pluggable scripts to the log4j configuration? Keep in mind that I do not know in advance what the names of the scripts are, so this has to happen at runtime.
Consider the following code:
import org.apache.log4j.Logger
// ...
Logger log = Logger.getLogger('MyPlugin')
new File( grailsApplication.config.externalFiles ).eachFile { file ->
Binding binding = new Binding()
binding.variables.log = log
GroovyShell shell = new GroovyShell(binding)
shell.evaluate(file)
strategies.put( binding.variables.key, binding.variables )
}
Explanation:
It is not obligatory to pass class name to getLogger, it can be actually any string. You just need to make sure that this string is matched in log4j.properties of the main program.
You pass once created log to plugin scripts via binding variable "log". Then plugin scripts can access it simply as log.info('test123')
My personal recommendation would be to use logback instead of log4j. Both libraries were developed by the same guy and it is stated that logback supersedes log4j.
Related
I recently switched my logback configuration file from logback.xml to logback.groovy. Using a DSL with Groovy is more versatile than XML for this sort of thing.
I need to analyse this file programmatically, like I analysed the previous XML file (any of innumerable parsing tools). I realise that this will be imperfect, as a DSL config file sits on top of an object which it configures and must be executed, so its results are inevitably dynamic, whereas an XML file is static.
If you want to include one Groovy file in another file there are solutions. This one worked for me.
But I'm struggling to find what I need from the results.
If I put a function like this in the DSL file ...
def greet(){
println "hello world"
}
... not only can I execute it (config.greet() as below), but I can also see it listed when I go
GroovyShell shell = new GroovyShell()
def config = shell.parse( logfileConfigPath.toFile() )
println "config.class.properties ${config.class.properties}"
But if I put a line like this in the DSL file...
def MY_CONSTANT = "XXX"
... I have no idea how to find it and get its value (it is absent from the confusing and copious output from config.class.properties).
PS printing out config.properties just gives this:
[class:class logback, binding:groovy.lang.Binding#564fa2b]
... and yes, I did look at config.binding.properties: there was nothing.
further thought
My question is, more broadly, about what if any tools are available for analysis of Groovy DSL configuration files. Given that such a file is pretty meaningless without the underlying object it is configuring (an object implementing org.gradle.api.Project in the case of Gradle; I don't know what class it may be in the case of logback), you would have thought there would need to be instrumentation to kind of hitch up such an object and then observe the effects of the config file in a controlled, observable way. If Groovy DSL config files are to be as versatile as their XML counterparts surely you need something along those lines? NB I have a suspicion that org.gradle.tooling.model.GradleProject or org.gradle.tooling.model.ProjectModel might serve that purpose. Unfortunately, at the current time I am unable to get GradleConnector working, as detailed here.
I presume there is nothing of this kind for logback, and at the moment I have no knowledge of its DSL or configurable object, or the latter's class or interface...
The use of def creates a local variable in the execution of the script that is not available in the binding of the script; see this. Even dropping def will not expose MY_CONSTANT in the binding because parsing the script via GroovyShell.parse() does not interpret/execute the code.
To expose MY_CONSTANT in config's binding, change def MY_CONSTANT = "XXX" to MY_CONSTANT = "XXX" and execute the config script via config.run().
In Grails 2.3.7 I'm using _Events.groovy to hook into WAR packaging to do some special processing:
_Events.groovy
import demo.utils.XmlUtil
eventCreateWarStart = { name, stageDir ->
XmlUtil.doSomething()
...
log.debug('done!')
}
When building the WAR, Grails complains about XmlUtil import statement. _Events.groovy is not a class, so import statements don't work. How can I use a custom class in a script if I can't import it? And how can I perform logging instead of using println?
Update
Loading classes manually based on this and this seems to do the trick, also got logging to work thanks to Aaron's answer below:
eventCreateWarStart = { name, stageDir ->
def xmlUtil = loadRequiredClass('demo.utils.XmlUtil')
xmlUtil.doSomething()
...
grailsConsole.log('done!')
}
loadRequiredClass = {classname ->
classLoader.loadClass(classname)
}
Questions
What are all implicit objects available to Grails scripts?
It's a pain but it does make sense when you think about it. The _Events.groovy is part of the build process which is also responsible for compiling the classes that you are trying to use in _Events.groovy. Definitely a catch-22 scenario but I don't see how it could be made better without splitting _Events.groovy into separate files that compile and load at different stages of the build process.
You can use grailsConsole.log("hi") or grailsConsole.updateStatus("hi") to log output to the console.
I just tried to read a grails config from groovy. The ConfigSlurper is easy to use, but since it executes the config, it needs all dependencies in place. In my case, it complains about a missing log4j class.
Even when I import this class into my ConfigSlurper script, the config itself runs into this problem.
Any Idea how I could make the log4j classes accessible to the config?
Update: now that I have a proper keyboard in front of me, I can elaborate on my problem:
I have grails configurations which configure log4j as described in the docs:
import org.apache.log4j.*
log4j = {
appenders {
appender new RollingFileAppender(
name: "myAppender",
maxFileSize: 1024,
file: "/tmp/logs/myApp.log"
)
}
}
Then I tried to parse them like this:
def file = new File(<config location>)
def config = new ConfigSlurper().parse(file.toURL())
and the Slurper threw an exception...
I've found two solutions:
First, the documents show a way how to configure a rollingFileAppender without instantiating one: http://grails.org/doc/2.3.7/guide/conf.html .
Second, I managed to fix my original problem with a
#Grab('log4j:log4j:')
in front of my script - not a good solution if you are behind a firewall...
As Jeff already stated, it should also be possible to put the right jar file in the classpath, but I have to say that I often struggle with the cp :-( and when I tried to put the log4j.jar in the .groovy folder, I didn't succeed - but I guess this was because of an inproper groovy installation.
In general I have my dsl as plugin and I want to create a new app that use my dsl
so i tried to write this code:
JsonParser p = new JsonParser();
IParseResult r = p.parse(new StringReader("{}"));
//once that work it will be the file data instead of {}
but when i do the parse the node model builder is null and the following line has exception:
return doParse(ruleName, in, nodeModelBuilder.get(), 0);
and i'm not sure how to init nodeModelBuilder
i'm sure i missing some steps but i'm not quite familiar with the xtext process.
thanks!
You already read the following answer on Eclipse Forum. You need to create an IParser instance by injecting it. All dependencies gets also injected. The necessary bindings are described in your JsonRuntimeModule. Xtext uses Guice and theses Modules to glue everything together. This pattern is called Dependency Injection.
... I want to create a new app that use my dsl
So you want to use your Json DSL in standalone mode.
My suggestion:
Create a minimum Eclipse IApplication with CLI that reads and parses an input file. The advantage of an Eclipse IApplication is that you can easily deploy an headless version of your DSL runtime. [1]
Have a look at your JsonInjectorProvider and the ParseHelper [2] from Xtext's JUnit support for examples how to use your DSL and Xtext runtime in standalone mode.
[1] http://www.eclipsezone.com/eclipse/forums/t99762.html
[2] org.eclipse.xtext.junit.util.ParseHelper
You are not supposed to call parser directly. See:
http://wiki.eclipse.org/Xtext/FAQ#How_do_I_load_my_model_in_a_standalone_Java_application.C2.A0.3F
The code should look like:
Injector injector = new MyDslStandaloneSetup().createInjectorAndDoEMFRegistration();
XtextResourceSet resourceSet = injector.getInstance(XtextResourceSet.class);
resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE);
Resource resource = resourceSet.getResource(new File("/../../some.json").toURI(), true);
Model modelRootElement = (Model) resource.getContents().get(0);
Replace MyDsl with 'JsonParser' or 'Json' or whatever is your DSL name. Look for class JsonStandaloneSetup or JsonParserStandaloneSetup in your DSL source code. This class is generated when you start the Xtext project (or when you run workflow for the first time, not sure now). Replace Model with whatever is your root element type. It must be EObject subclass.
The parsing/validation/buidling AST is done resource.getContents() command. Not very intuitive, I know. It is because you have to initialize context, all sorts of contexts i fact, Guice context, EMF context, and perhaps other, all encapsulated in the StandaloneSetup (and RuntimeModule). The context is similar to Spring Application Context.
You need to use StandaloneSetup to run in standalone mode.
See this tutorial for help
Section 3.4 of Grails documentation says that Grails app can be configured from an external source:
grails.config.locations = [
"classpath:${appName}-config.properties",
"classpath:${appName}-config.groovy",
"file:${userHome}/.grails/${appName}-config.properties",
"file:${userHome}/.grails/${appName}-config.groovy" ]
Also, it is possible to load config by specifying a class that is a config script:
grails.config.locations = [com.my.app.MyConfig]
My questions are:
Could you please give an example of how MyConfig class implementation could look like? It is not quite clear from the documentation.
If I want to use some external JSON REST service as a source for my config data, how can this be implemented?
Answer for second question: you can do that in BootStrap.groovy init closure, because basically, it allows you to execute any code:
// Inject grails app
def grailsApplication
def init = { servletContext ->
def externalValue = getItUsingRest(someUrl)
grailsApplication.config.my.custom.var = externalValue
}
Depending on version of grails you are using, you might need to use
org.codehaus.groovy.grails.commons.ConfigurationHolde.config
to get to config instead.
Your application configuration can actually be a Groovy script. So any class which looks like your Config.groovy can act as a configuration class. In our projects we tend to keep the configuration outside the grails application so that the application can be configured without recompiling everything.
Maybe this post of mine will give you a hint on how to use and load external configuration files.