Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
A grails plugin which creates a lightweight jar, with embedded jetty and a number of that conventions that make sense from Dropwizard
Java Groovy Shell
branch: master
Failed to load latest commit information.
grails-app/conf Fix AsyncRequestLog incorrect spacing.
scripts Added metrics dependencies new in 3.0.1
src Add support for rolling log files (#21)
test/unit/grails Merge branch 'master' into feature/bidi-gzip
wrapper upgraded to grails 2.2.3
.gitignore Update .gitignore to better support IntelliJ development
LICENSE Specified licensing to be apache 2.0
LightweightDeployGrailsPlugin.groovy Add Wotif as the organisation.
NOTICE
README.md Add support for rolling log files (#21)
application.properties Revert "-Upgrade version number"
grailsw upgraded to grails 2.2.3

README.md

grails-lightweight-deploy

This plugin is intended to produce a lightweight, production-ready, deployable grails application. It embeds jetty, and uses a number of the conventions from Dropwizard that make sense. This includes reading configuration from an externalised yml file, auto-instrumenting of controllers with codahale metrics and exposing a secondary port for the AdminServlet.

Getting Started

Add the plugin to your BuildConfig:

compile ":lightweight-deploy:latest"

ideally you should also replace the tomcat plugin with:

build ':jetty:2.0.3'

for the sake of consistency.

grails-lightweight-deploy uses logback for logging. This requires removing log4j usage from Grails by default. In order to do this, add the following to your global excludes in BuildConfig.groovy

excludes "log4j", "grails-plugin-log4j"

now create a logback.xml file in grails-app/conf:

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>
  <root level="info">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

This file will only be used when running grails commands locally. When the server is actually running on Jetty, it will use the configuration from your yml file. Finally, due to a long-standing bug in Grails, you need to:

grails run-app

once to get the logback.xml copied to the correct location inside /.grails. Note that this first build will spam with DEBUG messages, but commands subsequent will be fine.

Now you should be able to produce your executable jar file:

grails refresh-dependencies
grails lightweight

which will produce a jar file inside your target directory.

Next you need to create a yml file somewhere inside your source tree which will store your externalised configuration, something like the following:

http:
    port: 8080
    adminPort: 8048

logging:
    appenders:
      - type: file
        currentLogFilename: ./server.log
        rootLevel: INFO

execute:

java -jar tar/appName-date.jar path/to/config.yml

then:

tail -f ./server.log

and you should see your server starting up successfully.

Configuration

Here's a complete sample configuration file, including comments describing the properties:

http:
    #The port number that your grails application will be hosted on. By default this is http, however if the ssl block below is specified it will be https.
    port: 8080
    #A secondary port which will serve your administrative content. This should be firewalled off from external access. Check http://localhost:8048/ for what it provides.
    adminPort: 8048
    #The context path at which to run the application.  By default, the application will be run at the root context.
    contextPath: /
    #The minimum number of threads to keep active to serve requests
    minThreads: 8
    #The maximum number of threads to keep active to serve requests
    maxThreads: 128
    #If this block is specified, then the app will run over https (see port property for running in mixed mode)
    ssl:
        #The path to the keystore which will be used to encrypt traffic over SSL on the port.
        keyStore: /etc/pki/tls/jks/keystore.jks
        #The alias inside the keystore which will be used.
        certAlias: subdomain.domain.com
        #The path to a plain-text file which stores the password for the keystore. This is ops-friendly externalisation, who can then use another automation framework to manage the passwords.
        keyStorePasswordPath: /path/to/plain/text/file.txt
        #Overrides keyStorePasswordPath if specified, this is the password for the keystore
        keyStorePassword: password
        #If this is specified as well as http.port and it is different then the app will run in mixed mode (both HTTP and HTTPS)
        port: 8081
    #If specified, then an access log will be written.
    requestLog:
        appenders:
          #The type of appender (file or console).
          - type: file
            #The path to the file to write access log to.
            currentLogFilename: ./server_access_log.txt
            #Whether or not to archive old events in separate files.
            archive: true
            #Required if archive is true. The filename pattern for archived files. %d is replaced with the date in yyyy-MM-dd form, and the fact that it ends with .gz indicates the file will be gzipped as it’s archived. Likewise, filename patterns which end in .zip will be filled as they are archived.
            archivedLogFilenamePattern: ./server-access-log-%d.txt
            #The number of archived files to keep.
            archivedFileCount: 5 
    gzip:
        #If true, all requests with gzip in their Accept-Content-Encoding headers will have their response entities encoded with gzip.
        enabled: true
        #All response entities under this size are not compressed.
        minimumEntitySize: 256
        #The size of the buffer to use when compressing.
        bufferSize: 8192
        #The set of agents to exclude from compression.
        #excludedUserAgents:
        #If specified, the set of mime types to compress.
        #compressedMimeTypes:
        #If specified, compression will only be applied to these HTTP methods.  `GET` is always used, even if not specified.
        #includedMethods:
        #The set of agent patterns to exclude from compression.
        #excludedUserAgentPatterns:
        #If true, gzip-compatible deflation will be used.
        gzipCompatibleDeflation: true
        #The value of the Vary header sent with responses that could be compressed.
        vary: Accept-Encoding
        #The compression level used for deflate compression. (0-9).  See `java.util.zip.Deflater`.
        deflateCompressionLevel: -1

logging:
    #custom log levels - note that in previous versions loggers used to sit under file property, this will throw an exception now if the config remains after updating to this version 
    loggers:
        foo: ERROR
        bar.baz: DEBUG
    #The default log level - note that in previous versions this was called rootLevel and used to sit under file property, this will throw an exception now if the config remains after updating to this version
    level: INFO
    appenders:
      #Logs to a file.
      - type: file
        #The path to the file to write the server log to.
        currentLogFilename: ./server.log
        #Whether or not to archive old events in separate files.
        archive: true
        #Required if archive is true. The filename pattern for archived files. %d is replaced with the date in yyyy-MM-dd form, and the fact that it ends with .gz indicates the file will be gzipped as it’s archived. Likewise, filename patterns which end in .zip will be filled as they are archived.
        archivedLogFilenamePattern: ./server-%d.log
        #The number of archived files to keep.
        archivedFileCount: 5
        #The threshold over which log statements must be before being logged.
        threshold: ALL
        # the timezone to print dates in
        timeZone: GMT+10
        # specify a log format for the file log 
        logFormat: "%-5p [%d{ISO8601,GMT+10}] [%-30thread] %c: %m%n%xEx"
      #Logs to the console.
      - type: console
        # the timezone to print dates in (defaults to TimeZone.getDefault())
        timeZone: GMT+10
        # the threshold over which log statements must be before being logged.
        threshold: ALL
        # the format for the console log
        logFormat: "%-5p [%d{ISO8601,GMT+10}] [%-30thread] %c: %m%n%xEx"

#If this block is present, then a jmx server will be started on the given ports. Both are required.
jmx:
    serverPort: 10000
    registryPort: 10001

#If specified, this directory will be used for temporary work files whilst the server is running. Defaults to java.io.tmpdir.
workDir: ./work

Customised Bootstrapping

By default, the server will expose only the content in your grails application. It is possible to perform extra configuration of the Jetty server though. To do this, you need to write a custom Launcher. Here's a basic example:

package com.name;

public class ApplicationLauncher extends grails.plugin.lightweightdeploy.Launcher {

    public ApplicationLauncher(String configYmlPath) throws IOException {
        super(configYmlPath);
    }

    public static void main(String[] args) throws IOException {
        try {
            verifyArgs(args);
            new ApplicationLauncher(args[0]).start();
        } catch (Throwable e) {
            System.err.println("Failure launching application");
            e.printStackTrace();
            System.exit(1);
        }
    }
}

to get grails-lightweight-deploy to use this launcher, you then specify the following in your Config.groovy:

grails.plugin.lightweightdeploy.mainClass="com.name.ApplicationLauncher"

from there, you can perform any extra bootstrapping required.

If your bootstrapping relies on some extra dependencies, you can use the following:

grails.plugin.lightweightdeploy.extraDependencies = ["group:artifact:version", ...]

These jars will then be exploded into the bootstrapping portion of your runnable jar.

You can also include specific classes or directories of classes from your application into the bootstrapping portion of the runnable jar:

grails.plugin.lightweightdeploy.extraClasses = ["**/Awesome.class", "**/util/**"]

Extra configuration on the external or admin connectors

It is possible, with a custom Launcher, to perform extra configuration on the external or admin connectors. To do so you need to override either of the following methods:

protected void configureExternalServlets(WebAppContext context);
protected void configureInternalServlets(ServletContextHandler handler);

For example, to add an extra servlet to the external connector:

@Override
protected void configureExternalServlets(WebAppContext context) {
    super.configureExternalServlets(context);

    context.addServlet(new ServletHolder(new MyCustomServlet()), "/custom/url");
}

Similarly the plugin provides a default ThreadDeadlockHealthCheck at "http://server:adminPort/healthcheck" Custom HealthChecks can be added by overriding:

protected void configureHealthChecks()

Be sure to still call super when overriding these methods for the default behaviour.

Alternatively, if you need to add HealthChecks from a Spring-managed bean, or Resources.groovy, you can use the HealthCheckUtil class to get easy access to the HealthCheckRegistry.

Command-Line Arguments

By default, the artifact produced will have a name of the form: appName-appVersion-date (e.g. testapp-1.0-2013.01.01). This is fairly friendly, informational default. It is, however possible to configure the name of the artifact produced from the command line. You can either specify the entire artifact name:

grails prod lightweight --artifactName=testName

or you can specify just a "release" modifier, which will be appended to the standard default:

grails prod lightweight --release=123

Which would produce a name of the form appName-appVersion-date_123. This can be useful to set to the build number from your CI server, to make tracking of the build into production easier.

Metrics

lightweight-deploy automatically instruments all controller methods with Codahale Metrics. Each Timer is created dynamically as requests come in, named as "controllerName.actionName". The metrics are viewable at "http://server:adminPort/metrics".

You can access the MetricRegistry and create your own metrics using:

grails.plugin.lightweightdeploy.application.metrics.MetricsUtil.getMetricRegistry()

License

lightweight-deploy is licensed under Apache License 2.0

Credit

This plugin uses some code from both the standalone plugin by Burt Beckwith and Dropwizard by Codahale.

Something went wrong with that request. Please try again.