{"action":"created","comment":{"url":"https://api.github.com/repos/SuaveIO/suave/pulls/comments/37723914","id":37723914,"diff_hunk":"@@ -13,23 +15,45 @@ type LogLine =\n     level         : LogLevel\n     /// the source of the log line, e.g. 'ModuleName.FunctionName'\n     path          : string\n-    /// the message that the application wants to log\n-    message       : string\n+    /// the template message that the application wants to log\n+    template      : string\n+    /// a function which can format the log template message. When the\n+    /// message is pre-formatted, this is None. When the log message \n+    /// contains structured data, this function can be used to render\n+    /// the message template with the data.\n+    formatter     : LogTemplateFormatter option\n+    /// any key-value based data to log\n+    data          : Map<string, obj>\n     /// an optional exception\n     ``exception`` : exn option\n     /// timestamp when this log line was created\n     tsUTCTicks    : int64 }\n-    \n+\n+    /// Formats the log line as a string\n+    member x.formatMessage () =\n+        match x.formatter with\n+        | None -> x.template\n+        | Some fmter -> fmter x.template x.GetDataByName\n+\n+    /// Gets a data value\n+    member x.GetDataByName name = Option.toObj (x.data.TryFind name)\n+\n [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]\n module LogLine =\n   let mk path level trace ex message =\n-    { message       = message\n+    { template      = message\n+      formatter     = None\n+      data          = Map.empty\n       level         = level\n       path          = path\n       ``exception`` = ex\n       trace         = trace\n       tsUTCTicks  = Globals.utcNow().Ticks }\n \n+  /// assists with type inference, avoiding ugly 'box' calls for each\n+  /// value item when constructing the log data map \n+  let mapLogData (data: (string*obj) list) = Map.ofList data","path":"src/Suave/Logging/LogLine.fs","position":54,"original_position":54,"commit_id":"11d2c312768c7481b48d85c45cfb1417c0bde034","original_commit_id":"11d2c312768c7481b48d85c45cfb1417c0bde034","user":{"login":"haf","id":193115,"avatar_url":"https://avatars.githubusercontent.com/u/193115?v=3","gravatar_id":"","url":"https://api.github.com/users/haf","html_url":"https://github.com/haf","followers_url":"https://api.github.com/users/haf/followers","following_url":"https://api.github.com/users/haf/following{/other_user}","gists_url":"https://api.github.com/users/haf/gists{/gist_id}","starred_url":"https://api.github.com/users/haf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/haf/subscriptions","organizations_url":"https://api.github.com/users/haf/orgs","repos_url":"https://api.github.com/users/haf/repos","events_url":"https://api.github.com/users/haf/events{/privacy}","received_events_url":"https://api.github.com/users/haf/received_events","type":"User","site_admin":false},"body":"I think getting a nice API is a good idea. It's easy to provide overloads that transform primitive and common F# CLR values into the object model we both linked to, but not as easy when they are complex without letting the author of the type extend the type with an extra serialisation method.\r\n\r\n```\r\nmodule Value =\r\n  let ofCLRValue (data : ^a) =\r\n   // ...\r\n// and \r\nlet addData (m : Message) (key : String) (data : ^a) =\r\n Lens.setLens (Message.DataLens key) (Value.ofCLRValue data)\r\n```\r\n\r\nCould that work? I'm thinking we can provide the (bind, apply) applicatives for a new Value monad, like Chrion does, which lets you compose the values properly; thereby both defining the wire-format as well as letting MessageTemplates use all of the structure.\r\n\r\nAs for the structure; MessageTemplates is using a subset of the Value type that I linked to. How about we use the superset of mine in this library? That will allow supporting metrics easier, like is Logary's prerogative.","created_at":"2015-08-24T07:07:15Z","updated_at":"2015-08-24T07:07:15Z","html_url":"https://github.com/SuaveIO/suave/pull/288#discussion_r37723914","pull_request_url":"https://api.github.com/repos/SuaveIO/suave/pulls/288","_links":{"self":{"href":"https://api.github.com/repos/SuaveIO/suave/pulls/comments/37723914"},"html":{"href":"https://github.com/SuaveIO/suave/pull/288#discussion_r37723914"},"pull_request":{"href":"https://api.github.com/repos/SuaveIO/suave/pulls/288"}}},"pull_request":{"url":"https://api.github.com/repos/SuaveIO/suave/pulls/288","id":43114157,"html_url":"https://github.com/SuaveIO/suave/pull/288","diff_url":"https://github.com/SuaveIO/suave/pull/288.diff","patch_url":"https://github.com/SuaveIO/suave/pull/288.patch","issue_url":"https://api.github.com/repos/SuaveIO/suave/issues/288","number":288,"state":"open","locked":false,"title":"[WIP] Structured logging and log messages","user":{"login":"adamchester","id":570470,"avatar_url":"https://avatars.githubusercontent.com/u/570470?v=3","gravatar_id":"","url":"https://api.github.com/users/adamchester","html_url":"https://github.com/adamchester","followers_url":"https://api.github.com/users/adamchester/followers","following_url":"https://api.github.com/users/adamchester/following{/other_user}","gists_url":"https://api.github.com/users/adamchester/gists{/gist_id}","starred_url":"https://api.github.com/users/adamchester/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/adamchester/subscriptions","organizations_url":"https://api.github.com/users/adamchester/orgs","repos_url":"https://api.github.com/users/adamchester/repos","events_url":"https://api.github.com/users/adamchester/events{/privacy}","received_events_url":"https://api.github.com/users/adamchester/received_events","type":"User","site_admin":false},"body":"This is a WIP for discussion. This may eventually close #270.\r\n","created_at":"2015-08-23T02:21:07Z","updated_at":"2015-08-24T07:07:15Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b689e671d5872c44f69416ab8d9f5fddeb7288a4","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/SuaveIO/suave/pulls/288/commits","review_comments_url":"https://api.github.com/repos/SuaveIO/suave/pulls/288/comments","review_comment_url":"https://api.github.com/repos/SuaveIO/suave/pulls/comments{/number}","comments_url":"https://api.github.com/repos/SuaveIO/suave/issues/288/comments","statuses_url":"https://api.github.com/repos/SuaveIO/suave/statuses/11d2c312768c7481b48d85c45cfb1417c0bde034","head":{"label":"adamchester:log-structured-msg","ref":"log-structured-msg","sha":"11d2c312768c7481b48d85c45cfb1417c0bde034","user":{"login":"adamchester","id":570470,"avatar_url":"https://avatars.githubusercontent.com/u/570470?v=3","gravatar_id":"","url":"https://api.github.com/users/adamchester","html_url":"https://github.com/adamchester","followers_url":"https://api.github.com/users/adamchester/followers","following_url":"https://api.github.com/users/adamchester/following{/other_user}","gists_url":"https://api.github.com/users/adamchester/gists{/gist_id}","starred_url":"https://api.github.com/users/adamchester/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/adamchester/subscriptions","organizations_url":"https://api.github.com/users/adamchester/orgs","repos_url":"https://api.github.com/users/adamchester/repos","events_url":"https://api.github.com/users/adamchester/events{/privacy}","received_events_url":"https://api.github.com/users/adamchester/received_events","type":"User","site_admin":false},"repo":{"id":37250922,"name":"suave","full_name":"adamchester/suave","owner":{"login":"adamchester","id":570470,"avatar_url":"https://avatars.githubusercontent.com/u/570470?v=3","gravatar_id":"","url":"https://api.github.com/users/adamchester","html_url":"https://github.com/adamchester","followers_url":"https://api.github.com/users/adamchester/followers","following_url":"https://api.github.com/users/adamchester/following{/other_user}","gists_url":"https://api.github.com/users/adamchester/gists{/gist_id}","starred_url":"https://api.github.com/users/adamchester/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/adamchester/subscriptions","organizations_url":"https://api.github.com/users/adamchester/orgs","repos_url":"https://api.github.com/users/adamchester/repos","events_url":"https://api.github.com/users/adamchester/events{/privacy}","received_events_url":"https://api.github.com/users/adamchester/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/adamchester/suave","description":"Suave is a simple web development F# library providing a lightweight web server and a set of combinators to manipulate route flow and task composition.","fork":true,"url":"https://api.github.com/repos/adamchester/suave","forks_url":"https://api.github.com/repos/adamchester/suave/forks","keys_url":"https://api.github.com/repos/adamchester/suave/keys{/key_id}","collaborators_url":"https://api.github.com/repos/adamchester/suave/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/adamchester/suave/teams","hooks_url":"https://api.github.com/repos/adamchester/suave/hooks","issue_events_url":"https://api.github.com/repos/adamchester/suave/issues/events{/number}","events_url":"https://api.github.com/repos/adamchester/suave/events","assignees_url":"https://api.github.com/repos/adamchester/suave/assignees{/user}","branches_url":"https://api.github.com/repos/adamchester/suave/branches{/branch}","tags_url":"https://api.github.com/repos/adamchester/suave/tags","blobs_url":"https://api.github.com/repos/adamchester/suave/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/adamchester/suave/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/adamchester/suave/git/refs{/sha}","trees_url":"https://api.github.com/repos/adamchester/suave/git/trees{/sha}","statuses_url":"https://api.github.com/repos/adamchester/suave/statuses/{sha}","languages_url":"https://api.github.com/repos/adamchester/suave/languages","stargazers_url":"https://api.github.com/repos/adamchester/suave/stargazers","contributors_url":"https://api.github.com/repos/adamchester/suave/contributors","subscribers_url":"https://api.github.com/repos/adamchester/suave/subscribers","subscription_url":"https://api.github.com/repos/adamchester/suave/subscription","commits_url":"https://api.github.com/repos/adamchester/suave/commits{/sha}","git_commits_url":"https://api.github.com/repos/adamchester/suave/git/commits{/sha}","comments_url":"https://api.github.com/repos/adamchester/suave/comments{/number}","issue_comment_url":"https://api.github.com/repos/adamchester/suave/issues/comments{/number}","contents_url":"https://api.github.com/repos/adamchester/suave/contents/{+path}","compare_url":"https://api.github.com/repos/adamchester/suave/compare/{base}...{head}","merges_url":"https://api.github.com/repos/adamchester/suave/merges","archive_url":"https://api.github.com/repos/adamchester/suave/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/adamchester/suave/downloads","issues_url":"https://api.github.com/repos/adamchester/suave/issues{/number}","pulls_url":"https://api.github.com/repos/adamchester/suave/pulls{/number}","milestones_url":"https://api.github.com/repos/adamchester/suave/milestones{/number}","notifications_url":"https://api.github.com/repos/adamchester/suave/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/adamchester/suave/labels{/name}","releases_url":"https://api.github.com/repos/adamchester/suave/releases{/id}","created_at":"2015-06-11T09:09:25Z","updated_at":"2015-06-11T09:09:26Z","pushed_at":"2015-08-23T02:19:28Z","git_url":"git://github.com/adamchester/suave.git","ssh_url":"git@github.com:adamchester/suave.git","clone_url":"https://github.com/adamchester/suave.git","svn_url":"https://github.com/adamchester/suave","homepage":"http://suave.io","size":8742,"stargazers_count":0,"watchers_count":0,"language":"F#","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"SuaveIO:master","ref":"master","sha":"4f69e26db3ae4bf8cecb4577c83688c34e4eb3d1","user":{"login":"SuaveIO","id":5822862,"avatar_url":"https://avatars.githubusercontent.com/u/5822862?v=3","gravatar_id":"","url":"https://api.github.com/users/SuaveIO","html_url":"https://github.com/SuaveIO","followers_url":"https://api.github.com/users/SuaveIO/followers","following_url":"https://api.github.com/users/SuaveIO/following{/other_user}","gists_url":"https://api.github.com/users/SuaveIO/gists{/gist_id}","starred_url":"https://api.github.com/users/SuaveIO/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SuaveIO/subscriptions","organizations_url":"https://api.github.com/users/SuaveIO/orgs","repos_url":"https://api.github.com/users/SuaveIO/repos","events_url":"https://api.github.com/users/SuaveIO/events{/privacy}","received_events_url":"https://api.github.com/users/SuaveIO/received_events","type":"Organization","site_admin":false},"repo":{"id":755200,"name":"suave","full_name":"SuaveIO/suave","owner":{"login":"SuaveIO","id":5822862,"avatar_url":"https://avatars.githubusercontent.com/u/5822862?v=3","gravatar_id":"","url":"https://api.github.com/users/SuaveIO","html_url":"https://github.com/SuaveIO","followers_url":"https://api.github.com/users/SuaveIO/followers","following_url":"https://api.github.com/users/SuaveIO/following{/other_user}","gists_url":"https://api.github.com/users/SuaveIO/gists{/gist_id}","starred_url":"https://api.github.com/users/SuaveIO/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SuaveIO/subscriptions","organizations_url":"https://api.github.com/users/SuaveIO/orgs","repos_url":"https://api.github.com/users/SuaveIO/repos","events_url":"https://api.github.com/users/SuaveIO/events{/privacy}","received_events_url":"https://api.github.com/users/SuaveIO/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/SuaveIO/suave","description":"Suave is a simple web development F# library providing a lightweight web server and a set of combinators to manipulate route flow and task composition.","fork":false,"url":"https://api.github.com/repos/SuaveIO/suave","forks_url":"https://api.github.com/repos/SuaveIO/suave/forks","keys_url":"https://api.github.com/repos/SuaveIO/suave/keys{/key_id}","collaborators_url":"https://api.github.com/repos/SuaveIO/suave/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/SuaveIO/suave/teams","hooks_url":"https://api.github.com/repos/SuaveIO/suave/hooks","issue_events_url":"https://api.github.com/repos/SuaveIO/suave/issues/events{/number}","events_url":"https://api.github.com/repos/SuaveIO/suave/events","assignees_url":"https://api.github.com/repos/SuaveIO/suave/assignees{/user}","branches_url":"https://api.github.com/repos/SuaveIO/suave/branches{/branch}","tags_url":"https://api.github.com/repos/SuaveIO/suave/tags","blobs_url":"https://api.github.com/repos/SuaveIO/suave/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/SuaveIO/suave/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/SuaveIO/suave/git/refs{/sha}","trees_url":"https://api.github.com/repos/SuaveIO/suave/git/trees{/sha}","statuses_url":"https://api.github.com/repos/SuaveIO/suave/statuses/{sha}","languages_url":"https://api.github.com/repos/SuaveIO/suave/languages","stargazers_url":"https://api.github.com/repos/SuaveIO/suave/stargazers","contributors_url":"https://api.github.com/repos/SuaveIO/suave/contributors","subscribers_url":"https://api.github.com/repos/SuaveIO/suave/subscribers","subscription_url":"https://api.github.com/repos/SuaveIO/suave/subscription","commits_url":"https://api.github.com/repos/SuaveIO/suave/commits{/sha}","git_commits_url":"https://api.github.com/repos/SuaveIO/suave/git/commits{/sha}","comments_url":"https://api.github.com/repos/SuaveIO/suave/comments{/number}","issue_comment_url":"https://api.github.com/repos/SuaveIO/suave/issues/comments{/number}","contents_url":"https://api.github.com/repos/SuaveIO/suave/contents/{+path}","compare_url":"https://api.github.com/repos/SuaveIO/suave/compare/{base}...{head}","merges_url":"https://api.github.com/repos/SuaveIO/suave/merges","archive_url":"https://api.github.com/repos/SuaveIO/suave/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/SuaveIO/suave/downloads","issues_url":"https://api.github.com/repos/SuaveIO/suave/issues{/number}","pulls_url":"https://api.github.com/repos/SuaveIO/suave/pulls{/number}","milestones_url":"https://api.github.com/repos/SuaveIO/suave/milestones{/number}","notifications_url":"https://api.github.com/repos/SuaveIO/suave/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/SuaveIO/suave/labels{/name}","releases_url":"https://api.github.com/repos/SuaveIO/suave/releases{/id}","created_at":"2010-07-03T15:26:06Z","updated_at":"2015-08-23T02:52:27Z","pushed_at":"2015-08-23T02:21:08Z","git_url":"git://github.com/SuaveIO/suave.git","ssh_url":"git@github.com:SuaveIO/suave.git","clone_url":"https://github.com/SuaveIO/suave.git","svn_url":"https://github.com/SuaveIO/suave","homepage":"http://suave.io","size":11452,"stargazers_count":291,"watchers_count":291,"language":"F#","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":54,"mirror_url":null,"open_issues_count":43,"forks":54,"open_issues":43,"watchers":291,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/SuaveIO/suave/pulls/288"},"html":{"href":"https://github.com/SuaveIO/suave/pull/288"},"issue":{"href":"https://api.github.com/repos/SuaveIO/suave/issues/288"},"comments":{"href":"https://api.github.com/repos/SuaveIO/suave/issues/288/comments"},"review_comments":{"href":"https://api.github.com/repos/SuaveIO/suave/pulls/288/comments"},"review_comment":{"href":"https://api.github.com/repos/SuaveIO/suave/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/SuaveIO/suave/pulls/288/commits"},"statuses":{"href":"https://api.github.com/repos/SuaveIO/suave/statuses/11d2c312768c7481b48d85c45cfb1417c0bde034"}}}}