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/