Skip to content

Instantly share code, notes, and snippets.

@vahidhedayati
Last active May 29, 2017 14:03
Show Gist options
  • Save vahidhedayati/e5c839a534ce5fbd7b157bc617c44036 to your computer and use it in GitHub Desktop.
Save vahidhedayati/e5c839a534ce5fbd7b157bc617c44036 to your computer and use it in GitHub Desktop.
Grails application logging errors to slack

Grails slack plugin appears to do most of the work here

Visit: https://slack.com/ and create a new slack channel Goto Configure apps/integration and create a webhook Configure webhook url in the plugin

If you are using pre grails 2.3, get hold of the grails 2 project from : https://github.com/mathifonseca/grails-slack/tree/grails-2.x

Copy the content of the plugin https://github.com/mathifonseca/grails-slack/tree/grails-2.x/src/groovy/grails/plugin/slack & https://github.com/mathifonseca/grails-slack/tree/master/grails-app/services/grails/plugin/slack to your local application For grails 2.0.1:

Add to BuildConfig.groovy the following plugin:

  compile 'org.grails.plugins:rest-client-builder:2.0.1' 

Controller:

package testslack


class TestController {

    def messageService

    def index() {
        messageService.testService()
     //   messageService.runMe()
        render 'all done --- '
    }
}

Service:

package testslack

class MessageService {

    def slackService

	
	/**
	 * Sending to slack via log.error 
	 * log.error has been hacked using EventLogAppender which has been enabled in:
	 * bootstap and Config.groovy for error logs only
	 * When an error is thrown it calls on 
	 * @return
	 */
    def testService() {
        try {
            def u  = User.get(2L)
            println "-- ${u.name}"
        } catch (Throwable t) {
    		log.error(t.class.toString()+' threw following exception: '+t.toString()+' '+t.message+' '+new Date()+' no plugin used') 
        }
    }

	// Send to slack manually as required 
	def testService1() {
		try {
			def u  = User.get(2L)
			println "-- ${u.name}"
		} catch (Throwable t) {
			message(t.class.toString()+' threw following exception: '+t.toString()+' '+t.message+' '+new Date()+' no plugin used') 
		}
	}

    private void message(String t) {
        slackService.send {
            text "@vv "+t
            username 'badvad'
            channel '#general'
            markdown false

        }
    }
}

The initial method in the service uses log.error to initiate slack message: This was acheived by a slight modification to existing example:

http://www.stichlberger.com/software/grails-log-to-database-with-custom-log4j-appender/#codesyntax_4

My Config.groovy

// log4j configuration
log4j = {
	appenders {
		//EnhancedPatternLayout is needed in order to support the %throwable logging of stacktraces
		appender new EventLogAppender(source:'testslack', name: 'eventLogAppender', layout:new EnhancedPatternLayout(conversionPattern: '%d{DATE} %5p %c{1}:%L - %m%n %throwable{500}'), threshold: org.apache.log4j.Level.ERROR)
		console name:'stdout'
	}
	root {
		error 'eventLogAppender'
		info 'stdout'
	}
    error  'org.codehaus.groovy.grails.web.servlet',  //  controllers
           'org.codehaus.groovy.grails.web.pages', //  GSP
           'org.codehaus.groovy.grails.web.sitemesh', //  layouts
           'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
           'org.codehaus.groovy.grails.web.mapping', // URL mapping
           'org.codehaus.groovy.grails.commons', // core / classloading
           'org.codehaus.groovy.grails.plugins', // plugins
           'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
           'org.springframework',
           'org.hibernate',
           'net.sf.ehcache.hibernate'
}
slack {
	webhook = 'https://hooks.slack.com/services/xxxxxxxxxx/Xxxxxxx/xxxxxxxxxxxxx'
}

src/groovy/custom/EventLogAppender.groovy:

package custom

import grails.util.Holders

import org.apache.log4j.Appender
import org.apache.log4j.AppenderSkeleton
import org.apache.log4j.spi.LoggingEvent

	//Thanks to http://www.stichlberger.com/software/grails-log-to-database-with-custom-log4j-appender/#codesyntax_4

class EventLogAppender extends AppenderSkeleton implements Appender {
 
    static appInitialized = false
	
    String source
 
    @Override
    protected void append(LoggingEvent event) {
		//inject slack service 
		def slackService = Holders.grailsApplication.mainContext.getBean('slackService')
		
        if (appInitialized) {
            //copied from Log4J's JDBCAppender
            event.getNDC()
            event.getThreadName()
            // Get a copy of this thread's MDC.
            event.getMDCCopy()
            event.getLocationInformation()
            event.getRenderedMessage()
            event.getThrowableStrRep()
            def limit = { string, maxLength -> string.substring(0, Math.min(string.length(), maxLength))}
			
            String logStatement = getLayout().format(event);
			//send message to slack
			slackService.send {
				text "@vv "+logStatement
				username 'badvad'
				channel '#general'
				markdown false
	
			}
		}
    }
 
    /**
     * Set the source value for the logger (e.g. which application the logger belongs to)
     * @param source
     */
    public void setSource(String source) {
        this.source = source
    }
 
    @Override
    void close() {
        //noop
    }
 
    @Override
    boolean requiresLayout() {
        return true
    }
}

Bootstrap:

import custom.EventLogAppender

class BootStrap {

    def init = { servletContext ->
		EventLogAppender.appInitialized = true
    }
    def destroy = {
    }
}

As you can see EventLogger has been enabled in bootstrap followed by appender and error logs defined to go through new appender.

The new appender injects slackService (found in the plugin) and sends a message to the slack channel.

Whilst testing the plugin I also took out time to test out

Experiment 2: slack-webhook

compile group: 'net.gpedro.integrations.slack', name: 'slack-webhook', version: '1.2.1'

TestController / index

SlackApi api = new SlackApi("https://hooks.slack.com/services/xxxxxxxxxx/Xxxxxxx/xxxxxxxxxxxxx");
api.call(new SlackMessage("#general", "badvad", '@vv new message'));

This does send a message to room from given user but @user does not work comes back not shown as hyperlink in main chat room – actual user does not get mobile notifications either

Experiment 3: simple-slack-api

compile group: 'com.ullink.slack', name: 'simpleslackapi', version: '0.6.0'

This requires a web bot user to make things work:

https://api.slack.com/bot-users https://yourslackgroup.slack.com/apps/new/XXXXXX-bots

After putting in a name got a token: xxxxxxxxxxxxx TestController / index2

SlackSession session1 = SlackSessionFactory.createWebSocketSlackSession("xxxxxxxxxx/Xxxxxxx/xxxxxxxxxxxxx");
session1.connect();
SlackChannel channel = session1.findChannelByName("general"); //make sure bot is a member of the channel.
session1.sendMessage(channel, "hi im a bot" );
SlackUser user = session1.findUserByUserName("vv");
session1.sendMessageToUser(user, "Hi, how are you", null);

Strangely messages to the room did not appear to be sent but a direct message to a user did work. I got a message on phone from above which did not appear in the main room.

If you do end up importing the plugin to older versions of grails i.e. < 2.3, the please note a slight modification had to be made to rest call in the service it is commented out and changed to working version:

package slack

import slack.builder.SlackMessageBuilder
import slack.exception.SlackMessageException
import grails.plugins.rest.client.RestBuilder
import grails.converters.JSON
import java.nio.charset.Charset
import org.springframework.http.converter.StringHttpMessageConverter

class SlackService {

	def grailsApplication

    void send(Closure closure) throws SlackMessageException {

    	def message = buildMessage(closure)

    	def webhook = grailsApplication.config.slack.webhook

    	if (!webhook) throw new SlackMessageException("Slack webhook is not valid")

    	try {
    		webhook.toURL()
		} catch (Exception ex) {
			throw new SlackMessageException("Slack webhook is not valid")
		}

    	String jsonMessage = (message as JSON).toString()

    	log.debug "Sending message : ${jsonMessage}"

    	def rest = new RestBuilder()

		//rest.restTemplate.setMessageConverters([new StringHttpMessageConverter(Charset.forName("UTF-8"))])
		rest.restTemplate.setMessageConverters([new StringHttpMessageConverter()])

		def resp = rest.post(webhook.toString()) {
			header('Content-Type', 'application/json;charset=UTF-8')
			json jsonMessage
		}

		if (resp.status != 200 || resp.text != 'ok') {
			throw new SlackMessageException("Error while calling Slack -> ${resp.text}")
		}

    }

    private SlackMessage buildMessage(Closure closure) throws SlackMessageException {

    	def builder = new SlackMessageBuilder()
        closure.delegate = builder
        closure.resolveStrategy = Closure.DELEGATE_FIRST
        closure.call(builder)

        def message = builder?.message

        if (!message) throw new SlackMessageException("Cannot send empty message")

        return message

    }

}

Side notes: Shell script to send to slack via curl: https://gist.github.com/dopiaza/6449505 http://blog.pragbits.com/it/2015/02/09/slack-notifications-via-curl/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment