{"action":"created","comment":{"url":"https://api.github.com/repos/Graylog2/graylog2-server/pulls/comments/28051359","id":28051359,"diff_hunk":"@@ -0,0 +1,203 @@\n+/**\n+ * This file is part of Graylog.\n+ *\n+ * Graylog is free software: you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License as published by\n+ * the Free Software Foundation, either version 3 of the License, or\n+ * (at your option) any later version.\n+ *\n+ * Graylog is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ * GNU General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU General Public License\n+ * along with Graylog.  If not, see <http://www.gnu.org/licenses/>.\n+ */\n+package org.graylog2.events;\n+\n+import com.fasterxml.jackson.databind.ObjectMapper;\n+import com.google.common.annotations.VisibleForTesting;\n+import com.google.common.eventbus.DeadEvent;\n+import com.google.common.eventbus.EventBus;\n+import com.google.common.eventbus.Subscribe;\n+import com.google.common.util.concurrent.AbstractExecutionThreadService;\n+import com.google.common.util.concurrent.Uninterruptibles;\n+import com.mongodb.BasicDBList;\n+import com.mongodb.BasicDBObject;\n+import com.mongodb.BasicDBObjectBuilder;\n+import com.mongodb.Bytes;\n+import com.mongodb.DB;\n+import com.mongodb.DBCollection;\n+import com.mongodb.DBObject;\n+import com.mongodb.MongoException;\n+import org.graylog2.bindings.providers.MongoJackObjectMapperProvider;\n+import org.graylog2.database.MongoConnection;\n+import org.graylog2.plugin.system.NodeId;\n+import org.mongojack.DBCursor;\n+import org.mongojack.DBSort;\n+import org.mongojack.DBUpdate;\n+import org.mongojack.JacksonDBCollection;\n+import org.mongojack.WriteResult;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+import javax.inject.Inject;\n+import javax.inject.Named;\n+import javax.inject.Singleton;\n+import java.util.concurrent.TimeUnit;\n+\n+import static com.google.common.base.Preconditions.checkNotNull;\n+\n+@Singleton\n+public class ClusterEventService extends AbstractExecutionThreadService {\n+    private static final Logger LOG = LoggerFactory.getLogger(ClusterEventService.class);\n+\n+    @VisibleForTesting\n+    static final String COLLECTION_NAME = \"cluster_events\";\n+\n+    private final JacksonDBCollection<ClusterEvent, String> dbCollection;\n+    private final NodeId nodeId;\n+    private final ObjectMapper objectMapper;\n+    private final EventBus serverEventBus;\n+    private final EventBus clusterEventBus;\n+\n+    @Inject\n+    public ClusterEventService(final MongoJackObjectMapperProvider mapperProvider,\n+                               final MongoConnection mongoConnection,\n+                               final NodeId nodeId,\n+                               final ObjectMapper objectMapper,\n+                               final EventBus serverEventBus,\n+                               @Named(\"cluster_event_bus\") final EventBus clusterEventBus) {\n+        this(JacksonDBCollection.wrap(prepareCollection(mongoConnection), ClusterEvent.class, String.class, mapperProvider.get()),\n+                nodeId, objectMapper, serverEventBus, clusterEventBus);\n+    }\n+\n+    ClusterEventService(final JacksonDBCollection<ClusterEvent, String> dbCollection,\n+                        final NodeId nodeId,\n+                        final ObjectMapper objectMapper,\n+                        final EventBus serverEventBus,\n+                        final EventBus clusterEventBus) {\n+        this.nodeId = checkNotNull(nodeId);\n+        this.dbCollection = checkNotNull(dbCollection);\n+        this.objectMapper = checkNotNull(objectMapper);\n+        this.serverEventBus = checkNotNull(serverEventBus);\n+        this.clusterEventBus = checkNotNull(clusterEventBus);\n+\n+        this.clusterEventBus.register(this);\n+    }\n+\n+    @VisibleForTesting\n+    static DBCollection prepareCollection(final MongoConnection mongoConnection) {\n+        final DB db = mongoConnection.getDatabase();\n+\n+        DBCollection coll;\n+        if (!db.collectionExists(COLLECTION_NAME)) {\n+            final DBObject options = BasicDBObjectBuilder.start()\n+                    .add(\"capped\", true)\n+                    .add(\"size\", 32768)\n+                    .get();\n+\n+            try {\n+                coll = db.createCollection(COLLECTION_NAME, options);\n+            } catch (MongoException e) {\n+                // Error code 48 == \"collection already exists\"\n+                if(e.getCode() == 48) {\n+                    coll = db.getCollection(COLLECTION_NAME);\n+                } else {\n+                    throw e;\n+                }\n+            }\n+        } else {\n+            coll = db.getCollection(COLLECTION_NAME);\n+        }\n+\n+        if (!db.getCollection(COLLECTION_NAME).isCapped()) {\n+            LOG.warn(\"The MongoDB collection \\\"{}\\\" should be capped but isn't. \"\n+                    + \"Please drop the collection and restart Graylog\", COLLECTION_NAME);\n+        }\n+\n+        coll.createIndex(DBSort.asc(\"producer\"));\n+        coll.createIndex(DBSort.asc(\"consumers\"));\n+        coll.addOption(Bytes.QUERYOPTION_TAILABLE | Bytes.QUERYOPTION_AWAITDATA);\n+\n+        return coll;\n+    }\n+\n+    private DBCursor<ClusterEvent> eventCursor(NodeId nodeId) {\n+        // Resorting to ugly MongoDB Java Client because of https://github.com/devbliss/mongojack/issues/88\n+        final DBObject producerClause = new BasicDBObject(\"producer\", new BasicDBObject(\"$ne\", nodeId.toString()));\n+        final BasicDBList consumersList = new BasicDBList();\n+        consumersList.add(nodeId.toString());\n+        final DBObject consumersClause = new BasicDBObject(\"consumers\", new BasicDBObject(\"$nin\", consumersList));\n+        final BasicDBList and = new BasicDBList();\n+        and.add(producerClause);\n+        and.add(consumersClause);\n+        final DBObject query = new BasicDBObject(\"$and\", and);\n+\n+        return dbCollection.find(query)\n+                .sort(DBSort.asc(\"$natural\"))\n+                .addOption(Bytes.QUERYOPTION_TAILABLE)\n+                .addOption(Bytes.QUERYOPTION_AWAITDATA);\n+    }\n+\n+    private void updateConsumers(final String eventId, final NodeId nodeId) {\n+        final WriteResult<ClusterEvent, String> writeResult = dbCollection.updateById(eventId, DBUpdate.addToSet(\"consumers\", nodeId.toString()));\n+    }\n+\n+    private Object extractPayload(Object payload, String eventClass) {\n+        try {\n+            final Class<?> clazz = Class.forName(eventClass);\n+            return objectMapper.convertValue(payload, clazz);\n+        } catch (ClassNotFoundException e) {\n+            LOG.debug(\"Couldn't load class <\" + eventClass + \"> for event\", e);\n+            return null;\n+        } catch (IllegalArgumentException e) {\n+            LOG.debug(\"Error while deserializing payload\", e);\n+            return null;\n+\n+        }\n+    }\n+\n+    @Override\n+    protected void run() throws Exception {\n+        while (isRunning()) {\n+            try {\n+                LOG.debug(\"Opening MongoDB cursor on \\\"{}\\\"\", COLLECTION_NAME);\n+                final DBCursor<ClusterEvent> cursor = eventCursor(nodeId);\n+                while (cursor.hasNext()) {\n+                    ClusterEvent clusterEvent = cursor.next();\n+                    LOG.trace(\"Processing cluster event: {}\", clusterEvent);\n+\n+                    Object payload = extractPayload(clusterEvent.payload(), clusterEvent.eventClass());\n+                    if (payload != null) {\n+                        serverEventBus.post(payload);\n+                    } else {\n+                        LOG.warn(\"Couldn't extract payload of cluster event with ID <{}>\", clusterEvent.id());\n+                        LOG.debug(\"Invalid payload in cluster event: {}\", clusterEvent);\n+                    }\n+\n+                    updateConsumers(clusterEvent.id(), nodeId);\n+                }\n+            } catch (Exception e) {\n+                LOG.warn(\"Error while reading cluster events from MongoDB, retrying.\", e);\n+            }\n+\n+            // Don't overwhelm the server\n+            Uninterruptibles.sleepUninterruptibly(1L, TimeUnit.SECONDS);\n+        }\n+    }\n+\n+    @Subscribe\n+    public void publishClusterEvent(Object event) {\n+        if(event instanceof DeadEvent) {\n+            LOG.debug(\"Skipping DeadEvent on cluster event bus\");\n+            return;\n+        }\n+\n+        final String className = event.getClass().getCanonicalName();\n+        final ClusterEvent clusterEvent = ClusterEvent.create(nodeId.toString(), className, event);\n+        final String id = dbCollection.save(clusterEvent).getSavedId();","path":"graylog2-server/src/main/java/org/graylog2/events/ClusterEventService.java","position":null,"original_position":200,"commit_id":"1e388c7d86ea273a5880b5737233c049553075b3","original_commit_id":"dd382ed2cbd076ade8ea61728196cb8aded0fdcd","user":{"login":"joschi","id":43951,"avatar_url":"https://avatars.githubusercontent.com/u/43951?v=3","gravatar_id":"","url":"https://api.github.com/users/joschi","html_url":"https://github.com/joschi","followers_url":"https://api.github.com/users/joschi/followers","following_url":"https://api.github.com/users/joschi/following{/other_user}","gists_url":"https://api.github.com/users/joschi/gists{/gist_id}","starred_url":"https://api.github.com/users/joschi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joschi/subscriptions","organizations_url":"https://api.github.com/users/joschi/orgs","repos_url":"https://api.github.com/users/joschi/repos","events_url":"https://api.github.com/users/joschi/events{/privacy}","received_events_url":"https://api.github.com/users/joschi/received_events","type":"User","site_admin":false},"body":"We can't recover from the MongoException (except for queuing this event somewhere and retrying later, which increases complexity and isn't guaranteed to succeed), but we can at least log the exception and swallow it.","created_at":"2015-04-09T10:38:11Z","updated_at":"2015-04-09T10:38:11Z","html_url":"https://github.com/Graylog2/graylog2-server/pull/1099#discussion_r28051359","pull_request_url":"https://api.github.com/repos/Graylog2/graylog2-server/pulls/1099","_links":{"self":{"href":"https://api.github.com/repos/Graylog2/graylog2-server/pulls/comments/28051359"},"html":{"href":"https://github.com/Graylog2/graylog2-server/pull/1099#discussion_r28051359"},"pull_request":{"href":"https://api.github.com/repos/Graylog2/graylog2-server/pulls/1099"}}},"pull_request":{"url":"https://api.github.com/repos/Graylog2/graylog2-server/pulls/1099","id":32868545,"html_url":"https://github.com/Graylog2/graylog2-server/pull/1099","diff_url":"https://github.com/Graylog2/graylog2-server/pull/1099.diff","patch_url":"https://github.com/Graylog2/graylog2-server/pull/1099.patch","issue_url":"https://api.github.com/repos/Graylog2/graylog2-server/issues/1099","number":1099,"state":"open","locked":false,"title":"Simple cluster event bus","user":{"login":"joschi","id":43951,"avatar_url":"https://avatars.githubusercontent.com/u/43951?v=3","gravatar_id":"","url":"https://api.github.com/users/joschi","html_url":"https://github.com/joschi","followers_url":"https://api.github.com/users/joschi/followers","following_url":"https://api.github.com/users/joschi/following{/other_user}","gists_url":"https://api.github.com/users/joschi/gists{/gist_id}","starred_url":"https://api.github.com/users/joschi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joschi/subscriptions","organizations_url":"https://api.github.com/users/joschi/orgs","repos_url":"https://api.github.com/users/joschi/repos","events_url":"https://api.github.com/users/joschi/events{/privacy}","received_events_url":"https://api.github.com/users/joschi/received_events","type":"User","site_admin":false},"body":"This PR adds a simple cluster event bus based on [MongoDB capped collections](http://docs.mongodb.org/manual/core/capped-collections/) and [Guava EventBus](https://code.google.com/p/guava-libraries/wiki/EventBusExplained).\r\n\r\nThe cluster event bus can be used to send events from one Graylog node to all other Graylog nodes in the cluster, e. g. to trigger a configuration reload. All events should be consumed once by each node (also if an error occurred while processing the cluster event or its payload).\r\n\r\nUnderneath it all, the cluster event bus is using a capped collection in MongoDB which is regularly polled by each Graylog node (also see http://docs.mongodb.org/manual/tutorial/create-tailable-cursor/). Once the document has been processed by a particular node, its node ID is being added to the \"consumers\" array of the document so that it won't read it again. The deserialized payload of a cluster event is being posted to the local server EventBus so that other parts of the node can react upon it.\r\n\r\nA Graylog node will *not* process messages it produced itself, so any event that should be processed locally as well, has to be sent to the cluster *and* to the server event bus.\r\n\r\nEvery event being sent to the cluster is being wrapped inside a `ClusterEvent` and *must* be serializable and deserializable by Jackson, otherwise the receivers won't be able to deserialize the payload of the cluster event!","created_at":"2015-04-08T14:57:57Z","updated_at":"2015-04-09T10:38:11Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9366d932098cb52a39ba2d7cbf8629ca0473d4fd","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/Graylog2/graylog2-server/pulls/1099/commits","review_comments_url":"https://api.github.com/repos/Graylog2/graylog2-server/pulls/1099/comments","review_comment_url":"https://api.github.com/repos/Graylog2/graylog2-server/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Graylog2/graylog2-server/issues/1099/comments","statuses_url":"https://api.github.com/repos/Graylog2/graylog2-server/statuses/1e388c7d86ea273a5880b5737233c049553075b3","head":{"label":"Graylog2:cluster-events","ref":"cluster-events","sha":"1e388c7d86ea273a5880b5737233c049553075b3","user":{"login":"Graylog2","id":474892,"avatar_url":"https://avatars.githubusercontent.com/u/474892?v=3","gravatar_id":"","url":"https://api.github.com/users/Graylog2","html_url":"https://github.com/Graylog2","followers_url":"https://api.github.com/users/Graylog2/followers","following_url":"https://api.github.com/users/Graylog2/following{/other_user}","gists_url":"https://api.github.com/users/Graylog2/gists{/gist_id}","starred_url":"https://api.github.com/users/Graylog2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Graylog2/subscriptions","organizations_url":"https://api.github.com/users/Graylog2/orgs","repos_url":"https://api.github.com/users/Graylog2/repos","events_url":"https://api.github.com/users/Graylog2/events{/privacy}","received_events_url":"https://api.github.com/users/Graylog2/received_events","type":"Organization","site_admin":false},"repo":{"id":671825,"name":"graylog2-server","full_name":"Graylog2/graylog2-server","owner":{"login":"Graylog2","id":474892,"avatar_url":"https://avatars.githubusercontent.com/u/474892?v=3","gravatar_id":"","url":"https://api.github.com/users/Graylog2","html_url":"https://github.com/Graylog2","followers_url":"https://api.github.com/users/Graylog2/followers","following_url":"https://api.github.com/users/Graylog2/following{/other_user}","gists_url":"https://api.github.com/users/Graylog2/gists{/gist_id}","starred_url":"https://api.github.com/users/Graylog2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Graylog2/subscriptions","organizations_url":"https://api.github.com/users/Graylog2/orgs","repos_url":"https://api.github.com/users/Graylog2/repos","events_url":"https://api.github.com/users/Graylog2/events{/privacy}","received_events_url":"https://api.github.com/users/Graylog2/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Graylog2/graylog2-server","description":"Free and open source log management","fork":false,"url":"https://api.github.com/repos/Graylog2/graylog2-server","forks_url":"https://api.github.com/repos/Graylog2/graylog2-server/forks","keys_url":"https://api.github.com/repos/Graylog2/graylog2-server/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Graylog2/graylog2-server/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Graylog2/graylog2-server/teams","hooks_url":"https://api.github.com/repos/Graylog2/graylog2-server/hooks","issue_events_url":"https://api.github.com/repos/Graylog2/graylog2-server/issues/events{/number}","events_url":"https://api.github.com/repos/Graylog2/graylog2-server/events","assignees_url":"https://api.github.com/repos/Graylog2/graylog2-server/assignees{/user}","branches_url":"https://api.github.com/repos/Graylog2/graylog2-server/branches{/branch}","tags_url":"https://api.github.com/repos/Graylog2/graylog2-server/tags","blobs_url":"https://api.github.com/repos/Graylog2/graylog2-server/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Graylog2/graylog2-server/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Graylog2/graylog2-server/git/refs{/sha}","trees_url":"https://api.github.com/repos/Graylog2/graylog2-server/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Graylog2/graylog2-server/statuses/{sha}","languages_url":"https://api.github.com/repos/Graylog2/graylog2-server/languages","stargazers_url":"https://api.github.com/repos/Graylog2/graylog2-server/stargazers","contributors_url":"https://api.github.com/repos/Graylog2/graylog2-server/contributors","subscribers_url":"https://api.github.com/repos/Graylog2/graylog2-server/subscribers","subscription_url":"https://api.github.com/repos/Graylog2/graylog2-server/subscription","commits_url":"https://api.github.com/repos/Graylog2/graylog2-server/commits{/sha}","git_commits_url":"https://api.github.com/repos/Graylog2/graylog2-server/git/commits{/sha}","comments_url":"https://api.github.com/repos/Graylog2/graylog2-server/comments{/number}","issue_comment_url":"https://api.github.com/repos/Graylog2/graylog2-server/issues/comments{/number}","contents_url":"https://api.github.com/repos/Graylog2/graylog2-server/contents/{+path}","compare_url":"https://api.github.com/repos/Graylog2/graylog2-server/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Graylog2/graylog2-server/merges","archive_url":"https://api.github.com/repos/Graylog2/graylog2-server/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Graylog2/graylog2-server/downloads","issues_url":"https://api.github.com/repos/Graylog2/graylog2-server/issues{/number}","pulls_url":"https://api.github.com/repos/Graylog2/graylog2-server/pulls{/number}","milestones_url":"https://api.github.com/repos/Graylog2/graylog2-server/milestones{/number}","notifications_url":"https://api.github.com/repos/Graylog2/graylog2-server/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Graylog2/graylog2-server/labels{/name}","releases_url":"https://api.github.com/repos/Graylog2/graylog2-server/releases{/id}","created_at":"2010-05-17T21:27:02Z","updated_at":"2015-04-09T09:53:50Z","pushed_at":"2015-04-09T10:36:58Z","git_url":"git://github.com/Graylog2/graylog2-server.git","ssh_url":"git@github.com:Graylog2/graylog2-server.git","clone_url":"https://github.com/Graylog2/graylog2-server.git","svn_url":"https://github.com/Graylog2/graylog2-server","homepage":"https://www.graylog.org/","size":49130,"stargazers_count":1255,"watchers_count":1255,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":209,"mirror_url":null,"open_issues_count":199,"forks":209,"open_issues":199,"watchers":1255,"default_branch":"master"}},"base":{"label":"Graylog2:master","ref":"master","sha":"97b759cdae6f0f266eaf8d270214fda1d93bc038","user":{"login":"Graylog2","id":474892,"avatar_url":"https://avatars.githubusercontent.com/u/474892?v=3","gravatar_id":"","url":"https://api.github.com/users/Graylog2","html_url":"https://github.com/Graylog2","followers_url":"https://api.github.com/users/Graylog2/followers","following_url":"https://api.github.com/users/Graylog2/following{/other_user}","gists_url":"https://api.github.com/users/Graylog2/gists{/gist_id}","starred_url":"https://api.github.com/users/Graylog2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Graylog2/subscriptions","organizations_url":"https://api.github.com/users/Graylog2/orgs","repos_url":"https://api.github.com/users/Graylog2/repos","events_url":"https://api.github.com/users/Graylog2/events{/privacy}","received_events_url":"https://api.github.com/users/Graylog2/received_events","type":"Organization","site_admin":false},"repo":{"id":671825,"name":"graylog2-server","full_name":"Graylog2/graylog2-server","owner":{"login":"Graylog2","id":474892,"avatar_url":"https://avatars.githubusercontent.com/u/474892?v=3","gravatar_id":"","url":"https://api.github.com/users/Graylog2","html_url":"https://github.com/Graylog2","followers_url":"https://api.github.com/users/Graylog2/followers","following_url":"https://api.github.com/users/Graylog2/following{/other_user}","gists_url":"https://api.github.com/users/Graylog2/gists{/gist_id}","starred_url":"https://api.github.com/users/Graylog2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Graylog2/subscriptions","organizations_url":"https://api.github.com/users/Graylog2/orgs","repos_url":"https://api.github.com/users/Graylog2/repos","events_url":"https://api.github.com/users/Graylog2/events{/privacy}","received_events_url":"https://api.github.com/users/Graylog2/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Graylog2/graylog2-server","description":"Free and open source log management","fork":false,"url":"https://api.github.com/repos/Graylog2/graylog2-server","forks_url":"https://api.github.com/repos/Graylog2/graylog2-server/forks","keys_url":"https://api.github.com/repos/Graylog2/graylog2-server/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Graylog2/graylog2-server/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Graylog2/graylog2-server/teams","hooks_url":"https://api.github.com/repos/Graylog2/graylog2-server/hooks","issue_events_url":"https://api.github.com/repos/Graylog2/graylog2-server/issues/events{/number}","events_url":"https://api.github.com/repos/Graylog2/graylog2-server/events","assignees_url":"https://api.github.com/repos/Graylog2/graylog2-server/assignees{/user}","branches_url":"https://api.github.com/repos/Graylog2/graylog2-server/branches{/branch}","tags_url":"https://api.github.com/repos/Graylog2/graylog2-server/tags","blobs_url":"https://api.github.com/repos/Graylog2/graylog2-server/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Graylog2/graylog2-server/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Graylog2/graylog2-server/git/refs{/sha}","trees_url":"https://api.github.com/repos/Graylog2/graylog2-server/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Graylog2/graylog2-server/statuses/{sha}","languages_url":"https://api.github.com/repos/Graylog2/graylog2-server/languages","stargazers_url":"https://api.github.com/repos/Graylog2/graylog2-server/stargazers","contributors_url":"https://api.github.com/repos/Graylog2/graylog2-server/contributors","subscribers_url":"https://api.github.com/repos/Graylog2/graylog2-server/subscribers","subscription_url":"https://api.github.com/repos/Graylog2/graylog2-server/subscription","commits_url":"https://api.github.com/repos/Graylog2/graylog2-server/commits{/sha}","git_commits_url":"https://api.github.com/repos/Graylog2/graylog2-server/git/commits{/sha}","comments_url":"https://api.github.com/repos/Graylog2/graylog2-server/comments{/number}","issue_comment_url":"https://api.github.com/repos/Graylog2/graylog2-server/issues/comments{/number}","contents_url":"https://api.github.com/repos/Graylog2/graylog2-server/contents/{+path}","compare_url":"https://api.github.com/repos/Graylog2/graylog2-server/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Graylog2/graylog2-server/merges","archive_url":"https://api.github.com/repos/Graylog2/graylog2-server/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Graylog2/graylog2-server/downloads","issues_url":"https://api.github.com/repos/Graylog2/graylog2-server/issues{/number}","pulls_url":"https://api.github.com/repos/Graylog2/graylog2-server/pulls{/number}","milestones_url":"https://api.github.com/repos/Graylog2/graylog2-server/milestones{/number}","notifications_url":"https://api.github.com/repos/Graylog2/graylog2-server/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Graylog2/graylog2-server/labels{/name}","releases_url":"https://api.github.com/repos/Graylog2/graylog2-server/releases{/id}","created_at":"2010-05-17T21:27:02Z","updated_at":"2015-04-09T09:53:50Z","pushed_at":"2015-04-09T10:36:58Z","git_url":"git://github.com/Graylog2/graylog2-server.git","ssh_url":"git@github.com:Graylog2/graylog2-server.git","clone_url":"https://github.com/Graylog2/graylog2-server.git","svn_url":"https://github.com/Graylog2/graylog2-server","homepage":"https://www.graylog.org/","size":49130,"stargazers_count":1255,"watchers_count":1255,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":209,"mirror_url":null,"open_issues_count":199,"forks":209,"open_issues":199,"watchers":1255,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Graylog2/graylog2-server/pulls/1099"},"html":{"href":"https://github.com/Graylog2/graylog2-server/pull/1099"},"issue":{"href":"https://api.github.com/repos/Graylog2/graylog2-server/issues/1099"},"comments":{"href":"https://api.github.com/repos/Graylog2/graylog2-server/issues/1099/comments"},"review_comments":{"href":"https://api.github.com/repos/Graylog2/graylog2-server/pulls/1099/comments"},"review_comment":{"href":"https://api.github.com/repos/Graylog2/graylog2-server/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Graylog2/graylog2-server/pulls/1099/commits"},"statuses":{"href":"https://api.github.com/repos/Graylog2/graylog2-server/statuses/1e388c7d86ea273a5880b5737233c049553075b3"}}}}