hooks/caller: add caller hook for caller info
diff --git a/README.md b/README.md
index b6aa84c..7e06684 100644
--- a/README.md
+++ b/README.md
@@ -228,6 +228,9 @@
 * [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go)
   Send errors to the Papertrail hosted logging service via UDP.
 
+* [`github.com/Sirupsen/logrus/hooks/caller`](https://github.com/Sirupsen/logrus/blob/master/hooks/caller)
+  Include `caller=<file>:<line>` in log entries.
+
 * [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
   Send errors to remote syslog server.
   Uses standard library `log/syslog` behind the scenes.
diff --git a/hooks/caller/caller.go b/hooks/caller/caller.go
new file mode 100644
index 0000000..b966b68
--- /dev/null
+++ b/hooks/caller/caller.go
@@ -0,0 +1,36 @@
+package logrus_caller
+
+import (
+	"github.com/Sirupsen/logrus"
+	"path/filepath"
+	"runtime"
+	"strconv"
+	"strings"
+)
+
+type CallerHook struct {
+}
+
+func (hook *CallerHook) Fire(entry *logrus.Entry) error {
+	entry.Data["caller"] = hook.caller()
+	return nil
+}
+
+func (hook *CallerHook) Levels() []logrus.Level {
+	return []logrus.Level{
+		logrus.PanicLevel,
+		logrus.FatalLevel,
+		logrus.ErrorLevel,
+		logrus.WarnLevel,
+		logrus.InfoLevel,
+		logrus.DebugLevel,
+	}
+}
+
+func (hook *CallerHook) caller() string {
+	if _, file, line, ok := runtime.Caller(6); ok {
+		return strings.Join([]string{filepath.Base(file), strconv.Itoa(line)}, ":")
+	}
+	// not sure what the convention should be here
+	return ""
+}
diff --git a/hooks/caller/caller_test.go b/hooks/caller/caller_test.go
new file mode 100644
index 0000000..0bff1ff
--- /dev/null
+++ b/hooks/caller/caller_test.go
@@ -0,0 +1,41 @@
+package logrus_caller
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"testing"
+
+	"github.com/Sirupsen/logrus"
+)
+
+func LogAndAssertJSON(t *testing.T, log func(*logrus.Logger), assertions func(fields logrus.Fields)) {
+	var buffer bytes.Buffer
+	var fields logrus.Fields
+
+	logger := logrus.New()
+	logger.Hooks.Add(&CallerHook{})
+	logger.Out = &buffer
+	logger.Formatter = new(logrus.JSONFormatter)
+
+	log(logger)
+
+	err := json.Unmarshal(buffer.Bytes(), &fields)
+	if err != nil {
+		t.Error("Error unmarshaling log entry")
+	}
+
+	assertions(fields)
+}
+
+func TestCaller(t *testing.T) {
+	LogAndAssertJSON(t, func(logger *logrus.Logger) {
+		logger.Info("Hello World")
+	}, func(fields logrus.Fields) {
+		expected := "caller_test.go:33"
+
+		if fields["caller"] != expected {
+			t.Error(fmt.Sprintf("Caller was %s, expected %s", fields["caller"], expected))
+		}
+	})
+}