Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Bump to 0.7.0

  • Loading branch information...
commit 89ca1c4663d5fa28ee366fc2b03ad38097a9fd67 1 parent 8ab93e7
@zk authored
View
13 README
@@ -1,13 +0,0 @@
-# clj-exceptional
-
-FIXME: write description
-
-## Usage
-
-FIXME: write
-
-## License
-
-Copyright (C) 2010 FIXME
-
-Distributed under the Eclipse Public License, the same as Clojure.
View
89 README.md
@@ -0,0 +1,89 @@
+# clj-exceptional
+
+A Clojure client for [Exceptional](http://getexceptional.com).
+
+## Usage
+
+### Lein / Cake
+
+ [clj-exceptional "0.7.0"]
+
+### Importing
+
+ (ns foo
+ (:require [clj-exceptional :as cx
+
+ ;; or
+
+ (ns foo
+ (:use cljs-exceptional))
+
+For the examples below I'll assume you're using the require method above.
+
+### Basic Usage
+
+Set your key (!!IMPORTANT!!):
+
+ (cx/key! exceptional_app_api_key")
+
+Post an exception (blocking):
+
+ (cx/post-exc (Exception. "something went wrong!"))
+
+Post an exception (non-blocking using agents):
+
+ (cx/post-exc-async (Exception. "something went wrong!"))
+
+
+### "Catch" Macros
+
+Wrap some code (`catch-exc` returns the caught exception):
+
+ (cx/catch-exc
+ (op-throw-exception))
+
+ ;; => #<Exception>
+
+Wrap some code ('rethrow-exc` re-throws the caught exception):
+
+ (try
+ (cx/rethrow-exc
+ (op-throws-exception))
+ (catch Exception e (println "Something went wrong!!!)))
+
+ ;; => nil
+
+### Ring Handler
+
+clj-exceptional contains ring handlers (`wrap-exceptional-catch` and
+`wrap-exceptional-rethrow`) that will add information from
+the request map to the post.
+
+ (def ring-app
+ (-> routes
+ (wrap-params)
+ (wrap-file "resources/public")
+ (wrap-file-info)
+ (cx/wrap-exceptional-catch))
+
+This will send an exceptional post containing request parameters such
+as `:remote-addr`, `:uri`, and `:headers`.
+
+
+### Contributing
+
+Open up an issue or send me a pull request.
+
+
+### TODO
+
+* Expose more of ring request map entries, such as `:scheme`,
+ `:content-length`, and `:port`.
+
+
+
+## License
+
+Copyright (C) 2010 Zachary Kim
+
+Distributed under the Eclipse Public License, the same as Clojure.
View
9 project.clj
@@ -1,4 +1,7 @@
-(defproject clj-exceptional "1.0.0-SNAPSHOT"
- :description "FIXME: write"
+(defproject clj-exceptional "0.7.0"
+ :description "Clojure client for Exceptional http://getexceptional.com"
:dependencies [[org.clojure/clojure "1.2.0"]
- [org.clojure/clojure-contrib "1.2.0"]])
+ [org.clojure/clojure-contrib "1.2.0"]
+ [cheshire "1.1.0"]
+ [clj-http "0.1.3"]]
+ :dev-dependencies [[swank-clojure "1.2.0"]])
View
237 src/clj_exceptional.clj
@@ -0,0 +1,237 @@
+(ns clj-exceptional
+ (:require [cheshire.core :as che]
+ [clj-http.client :as client]
+ [clj-http.util :as cutil])
+ (:import [java.text SimpleDateFormat]))
+
+(def *root-dir* (.getAbsolutePath (java.io.File. "")))
+(def date-format "yyyy-MM-dd'T'HH:mm:ssZZZZ")
+(def date-formatter (SimpleDateFormat. date-format))
+(def _api-key (atom ""))
+(def _language (atom "clojure"))
+(def _language-version (atom (clojure-version)))
+
+(defn key! [new-key]
+ (reset! _api-key new-key))
+
+(defmacro with-key [key & body]
+ `(binding [_api-key (atom ~key)]
+ ~@body))
+
+(defn format-timestamp [ts]
+ (.format date-formatter ts))
+
+(defn now-iso []
+ (format-timestamp (System/currentTimeMillis)))
+
+(def client-info
+ {:name "clj-exceptional"
+ :version "0.7.0"
+ :protocol_version "6"})
+
+(def shell-env (reduce
+ #(assoc %1 (.getKey %2) (.getValue %2))
+ {}
+ (System/getenv)))
+
+(def java-env (let [props (System/getProperties)]
+ (reduce #(assoc %1 %2 (.getProperty props %2))
+ {}
+ (.stringPropertyNames props))))
+
+;; Threaders
+
+(defmacro defex [name & path]
+ `(defn ~name [m# val#]
+ (assoc-in m# [~@path] val#)))
+
+;; Require exceptional infos
+;; + exception/backtrace
+;; + exception/exception_class
+;; + exception/message
+;; + exception/occured_at x
+;; + application_environment/application_root_directory
+;; + application_environment/env
+
+(defn clean-stacktrace [^Exception e]
+ (->> (.getStackTrace e)
+ (seq)
+ (map #(.toString %))))
+
+(defmacro appenv [m & forms]
+ `(assoc ~m
+ :application_environment
+ (-> (:application_environment ~m)
+ ~@forms)))
+
+(defex framework :framework)
+(defex env :env)
+(defex language :language)
+(defex language-version :language_version)
+(defex root-dir :application_root_directory)
+
+(defmacro exception [m & forms]
+ `(assoc ~m
+ :exception
+ (-> (:exception ~m)
+ ~@forms)))
+
+(defex occured :occured_at)
+(defex message :message)
+(defex backtrace :backtrace)
+(defex exc-cls :exception_class)
+
+(defmacro request [m & forms]
+ `(assoc ~m
+ :request
+ (-> (:request ~m)
+ ~@forms)))
+
+(defex session :session)
+(defex remote-ip :remote_ip)
+(defex params :parameters)
+(defex action :action)
+(defex url :url)
+(defex req-method :request_method)
+(defex controller :controller)
+(defex headers :headers)
+
+
+(defn format-exception
+ ([^Exception e]
+ (-> {}
+ (exception
+ (backtrace (clean-stacktrace e))
+ (exc-cls (.getCanonicalName (class e)))
+ (message (.getMessage e))
+ (occured (now-iso)))
+ (appenv
+ (root-dir *root-dir*)
+ (env java-env)
+ (language @_language)
+ (language-version @_language-version))
+ (assoc :client client-info))))
+
+;;
+;; Usage
+;;
+
+(defn post [exc-map]
+ (client/post "http://api.getexceptional.com/api/errors"
+ {:query-params {:api_key @_api-key
+ :protocol_version "6"}
+ :body (cutil/gzip (.getBytes (che/generate-string exc-map)))}))
+
+(defn post-exc [^Exception e]
+ (post (format-exception e)))
+
+(defn post-async [exc-map]
+ (send-off (agent exc-map) post))
+
+(defn post-exc-async [^Exception e]
+ (post-async (format-exception e)))
+
+(defmacro catch-exc
+ "Catch exception, post to Exceptional, return exception."
+ [& body]
+ `(try
+ ~@body
+ (catch Exception e# (post-exc e#) e#)))
+
+(defmacro catch-mod
+ [mod & body]
+ `(try
+ ~@body
+ (catch Exception e# (do (post-async
+ (-> (format-exception e#)
+ ~mod))
+ e#))))
+
+(defmacro rethrow-exc
+ "Catch exception, post to Exceptional, re-throw."
+ [& body]
+ `(try
+ ~@body
+ (catch Exception e# (do (post-exc-async e#)
+ (throw e#)))))
+
+(defmacro rethrow-mod
+ [mod & body]
+ `(try
+ ~@body
+ (catch Exception e# (do (post
+ (-> (format-exception e#)
+ ~mod))
+ (throw e#)))))
+
+(defn add-request [exc-map req]
+ (request exc-map
+ (session (:session req))
+ (remote-ip (:remote-addr req))
+ (params (or (:params req)
+ (merge (:query-params req)
+ (:form-params req))))
+ (url (:uri req))
+ (req-method (:request-method req))
+ (headers (:headers req))))
+
+
+(defn with-exceptional-catch
+ "Ring middleware for posting all exceptions to Exceptional. Exceptions
+ are rethrown after posting. Adds request data to exceptional map."
+ [handler]
+ (fn [req]
+ (catch-mod
+ (add-request req)
+ (handler req))))
+
+(defn with-exceptional-rethrow
+ "Ring middleware for posting all exceptions to Exceptional. Exceptions
+ are rethrown after posting. Adds request data to exceptional map."
+ [handler]
+ (fn [req]
+ (rethrow-mod
+ (add-request req)
+ (handler req))))
+
+(comment
+
+ ;; Examples from README
+
+ ;; Please don't spam my account.
+
+ (key! "cfaabdee74ffc4d3e7c35391b0079629091f21c9")
+
+ (post-exc (Exception. "first example"))
+ (post-exc-async (Exception. "second example"))
+
+ (catch-exc
+ (throw (Exception. "third example")))
+
+ (rethrow-exc
+ (throw (Exception. "fourth example")))
+
+ (catch-mod
+ (add-request {:remote-addr "0:0:0:0:0:0:0:1%0",
+ :scheme :http,
+ :query-params {},
+ :session {"key" "val"},
+ :form-params {},
+ :multipart-params {},
+ :request-method :get,
+ :query-string nil,
+ :content-type nil,
+ :cookies
+ {"ring-session" {:value "3862a562-7d4d-448f-92d3-59f786023fa4"},
+ "remember_user_token"
+ {:value
+ "BAhbB2kGSSIZdVlrUGtBY0tuLVk5TGc2UkJRMF8GOgZFVA==--68edd1936590b325f476c5519198cee28afbb336"}},
+ :uri "/foobar",
+ :server-name "localhost",
+ :params {:foo "bar" :baz "bap"},
+ :headers
+ {"hkey" "hval"},
+ :content-length nil,
+ :server-port 8080,
+ :character-encoding nil})
+ (throw (Exception. "fifth example"))))
View
6 test/clj_exceptional/test/core.clj
@@ -1,6 +0,0 @@
-(ns clj-exceptional.test.core
- (:use [clj-exceptional.core] :reload)
- (:use [clojure.test]))
-
-(deftest replace-me ;; FIXME: write
- (is false "No tests have been written."))
View
95 test/test_clj_exceptional.clj
@@ -0,0 +1,95 @@
+(ns test-clj-exceptional
+ (:use clj-exceptional :reload)
+ (:use clojure.test))
+
+(deftest test-appenv
+ (let [res (:application_environment
+ (appenv {}
+ (framework "rails")
+ (env {"foo" "bar"})
+ (language "ruby")
+ (language-version "1.8.7")
+ (root-dir "/foo/bar")))]
+ (are [v k] (= v (k res))
+ "rails" :framework
+ {"foo" "bar"} :env
+ "ruby" :language
+ "1.8.7" :language_version
+ "/foo/bar" :application_root_directory)))
+
+
+(deftest test-exception
+ (let [res (:exception
+ (exception {}
+ (occured "now")
+ (message "foo")
+ (backtrace ["foo" "bar"])
+ (exc-cls "some.Class")))]
+ (are [v k] (= v (k res))
+ "now" :occured_at
+ "foo" :message
+ ["foo" "bar"] :backtrace
+ "some.Class" :exception_class)))
+
+
+(deftest test-request
+ (let [res (:request
+ (request {}
+ (session ["key" "val"])
+ (remote-ip "1234")
+ (params {"foo" "bar"})
+ (action "index")
+ (url "/index")
+ (req-method "GET")
+ (controller "main")
+ (headers {"baz" "bap"})))]
+ (are [v k] (= v (k res))
+ ["key" "val"] :session
+ "1234" :remote_ip
+ {"foo" "bar"} :parameters
+ "index" :action
+ "/index" :url
+ "GET" :request_method
+ "main" :controller
+ {"baz" "bap"} :headers)))
+
+(deftest test-format-exception
+ (let [res (format-exception (Exception. "test exception"))]
+ (is (:application_environment res))
+ (is (:exception res))
+ (is (:client res))))
+
+(def ex-req-map {:remote-addr "0:0:0:0:0:0:0:1%0",
+ :scheme :http,
+ :query-params {},
+ :session {"key" "val"},
+ :form-params {},
+ :multipart-params {},
+ :request-method :get,
+ :query-string nil,
+ :content-type nil,
+ :cookies
+ {"ring-session" {:value "3862a562-7d4d-448f-92d3-59f786023fa4"},
+ "remember_user_token"
+ {:value
+ "BAhbB2kGSSIZdVlrUGtBY0tuLVk5TGc2UkJRMF8GOgZFVA==--68edd1936590b325f476c5519198cee28afbb336"}},
+ :uri "/foobar",
+ :server-name "localhost",
+ :params {:foo "bar" :baz "bap"},
+ :headers
+ {"hkey" "hval"},
+ :content-length nil,
+ :server-port 8080,
+ :character-encoding nil})
+
+(deftest test-add-request
+ (let [res (:request (add-request {} ex-req-map))]
+ (are [v k] (= v (k res))
+ {"key" "val"} :session
+ "0:0:0:0:0:0:0:1%0" :remote_ip
+ {:foo "bar" :baz "bap"} :parameters
+ "/foobar" :url
+ {"hkey" "hval"} :headers)))
+
+
+
Please sign in to comment.
Something went wrong with that request. Please try again.