|
| 1 | +| title: Announcing: Snap Server v0.10.0.0 |
| 2 | +| author: Robert Massaioli <robertmassaioli@gmail.com> |
| 3 | +| published: 2015-06-12T06:10:00+1000 |
| 4 | +| updated: 2015-06-12T06:10:00+1000 |
| 5 | +| summary: Release notes for Snap Server v0.10.0.0 |
| 6 | + |
| 7 | +The snap team is happy to announce the release of version 0.10.0.0 of snap-server. |
| 8 | + |
| 9 | +## Changes |
| 10 | + |
| 11 | +This release of snap-server only contains one major change from 0.9.5.0 and that is the addition of |
| 12 | +custom logging. |
| 13 | + |
| 14 | +### Custom Access and Error logging for Snap Server |
| 15 | + |
| 16 | +In this release of snap-server we include two new configuration options aimed at letting you control |
| 17 | +the logging format of snap-server. |
| 18 | + |
| 19 | +Currently snap-server access logs appear in a format that resembles a common Apache httpd log format. For example, |
| 20 | +it should be familiar to see a log line like the following from your snap application: |
| 21 | + |
| 22 | + 127.0.0.1 - - [10/Jun/2015:14:12:55 +1000] "POST /rest/round-rooms HTTP/1.1" 200 - "http://localhost:8080/panel/hackathon-round-transition?project_id=10000&project_key=SP" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.81 Safari/537.36" |
| 23 | + |
| 24 | +And the error log is logged in a similar, but not quite the same, format and looks like: |
| 25 | + |
| 26 | + [04/Dec/2014:11:11:57 +1100] Server.httpServe: START, binding to [http://0.0.0.0:8080/] |
| 27 | + |
| 28 | +With the latest release of snap-server you are now free to define your own custom logging format! If |
| 29 | +you choose to not define you own custom format then the access and error logs will continue to |
| 30 | +appear in the same format. |
| 31 | + |
| 32 | +### Custom Access Logging (a working example) |
| 33 | + |
| 34 | +To create you own custom access and error logging formats snap-server provides two new configuration |
| 35 | +options setAccessLogHandler and setErrorLogHandler respectively. These methods will let you control |
| 36 | +how the access and error log lines are rendered. Lets run through a quick example with the |
| 37 | +setAccessLogHandler method. But first lets look at the types! The first type we will look at is: |
| 38 | + |
| 39 | + setAccessLogHandler :: AccessLogHandler -> Config m a -> Config m a |
| 40 | + |
| 41 | +This method accepts an AccessLogHandler and updates your snap-server configuration to use it. But |
| 42 | +what is an AccessLogHandler? An AccessLogHandler lets you get contextual information from the |
| 43 | +current request / response and output access log lines as a result and thus is defined as the |
| 44 | +following simple type alias: |
| 45 | + |
| 46 | + type AccessLogHandler = Request -> Response -> IO ByteString |
| 47 | + |
| 48 | +As you can see the AccessLogHandler is passed the Snap Request and Response so that you can log |
| 49 | +whatever you like in a context sensitive manner. Lets now show a working example of actually using |
| 50 | +these new types to log in JSON format using the [Aeson][1] library. First lets define a datatype that |
| 51 | +will represent the JSON data that should appear on each line of our log file: |
| 52 | + |
| 53 | + -- import qualified Data.Text as T |
| 54 | + data AccessLogLine = AccessLogLine |
| 55 | + { allEvent :: String |
| 56 | + , allTimestamp :: UTCTime |
| 57 | + , allHost :: T.Text |
| 58 | + , allMethod :: T.Text |
| 59 | + , allUrl :: T.Text |
| 60 | + , allHttpVersion :: T.Text |
| 61 | + , allStatus :: Int |
| 62 | + , allResponseContentLength :: Maybe Int64 |
| 63 | + , allReferer :: Maybe T.Text |
| 64 | + , allUserAgent :: Maybe T.Text |
| 65 | + } deriving (Show, Generic) |
| 66 | + |
| 67 | +As you can see this definition contains a lot of information about the request and response. We can |
| 68 | +now define a ToJSON instance for this data type so that we can encode it easily. We will use the |
| 69 | +derived Generic instance and a few helper methods to define the ToJSON instance: |
| 70 | + |
| 71 | + instance ToJSON AccessLogLine where |
| 72 | + toJSON = genericToJSON defaultOptions |
| 73 | + { fieldLabelModifier = stripFieldNamePrefix "all" |
| 74 | + } |
| 75 | + |
| 76 | + stripFieldNamePrefix :: String -> String -> String |
| 77 | + stripFieldNamePrefix pre = lowerFirst . try (L.stripPrefix pre) |
| 78 | + |
| 79 | + lowerFirst :: String -> String |
| 80 | + lowerFirst (x : xs) = C.toLower x : xs |
| 81 | + lowerFirst [] = [] |
| 82 | + |
| 83 | +Now it is a simple matter of defining the AccessLogHandler itself which will take the request and |
| 84 | +response, put the relevant data into an AccessLogLine and then return the encoded version of the |
| 85 | +AccessLogLine in JSON format: |
| 86 | + |
| 87 | + -- import qualified Data.Text as T |
| 88 | + -- import qualified Data.Text.Encoding as T |
| 89 | + -- import qualified Snap.Core as SC |
| 90 | + -- import qualified Snap.Types.Headers as H |
| 91 | + accessLogHandler :: AccessLogHandler |
| 92 | + accessLogHandler req rsp = do |
| 93 | + let hdrs = SC.headers req |
| 94 | + let host = SC.rqRemoteAddr req |
| 95 | + let (v, v') = SC.rqVersion req |
| 96 | + let referer = head <$> H.lookup "referer" hdrs |
| 97 | + let userAgent = head <$> H.lookup "user-agent" hdrs |
| 98 | + |
| 99 | + currentTime <- getCurrentTime |
| 100 | + return . L.toStrict . encode $ AccessLogLine |
| 101 | + { allEvent = "log.access" |
| 102 | + , allTimestamp = currentTime |
| 103 | + , allHost = t host |
| 104 | + , allMethod = tshow . SC.rqMethod $ req |
| 105 | + , allUrl = t . SC.rqURI $ req |
| 106 | + , allHttpVersion = T.concat [ tshow v, ".", tshow v' ] |
| 107 | + , allStatus = SC.rspStatus rsp |
| 108 | + , allResponseContentLength = rspContentLength rsp |
| 109 | + , allReferer = t <$> referer |
| 110 | + , allUserAgent = t <$> userAgent |
| 111 | + } |
| 112 | + where |
| 113 | + tshow :: Show a => a -> T.Text |
| 114 | + tshow = T.pack . show |
| 115 | + t = T.decodeUtf8 |
| 116 | + |
| 117 | +As you can see there is nothing overly complicated going on inside our accessLogHandler but now we |
| 118 | +can use the following code to update our snap-server configuration: |
| 119 | + |
| 120 | + setAccessLogHandler accessLogHandler |
| 121 | + |
| 122 | +And now the access logs for our snap application running on snap-server are being output in JSON |
| 123 | +format! You are now free to reuse this code in your own applications or to define your own custom |
| 124 | +formats. Please let us know what formats you were able to define that were the most useful to you. |
| 125 | + |
| 126 | +We hope that this change allows for better integration with third party tools or simply allows you to |
| 127 | +extract the information that you need from snap-server. Good luck and keep writing awesome web |
| 128 | +apps in snap! |
| 129 | + |
| 130 | + [1]: http://hackage.haskell.org/package/aeson |
0 commit comments