{"action":"created","comment":{"url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls/comments/141216301","pull_request_review_id":65395067,"id":141216301,"diff_hunk":"@@ -16,296 +16,348 @@\n \n 'use strict';\n \n+import { Logger } from '@google-cloud/common';\n import * as cls from './cls';\n import { Constants } from './constants';\n+import * as is from 'is';\n+import * as semver from 'semver';\n import { SpanData } from './span-data';\n import { Trace } from './trace';\n import { TraceLabels } from './trace-labels';\n import * as TracingPolicy from './tracing-policy';\n import * as util from './util';\n-\n-var is = require('is');\n-var uuid = require('uuid');\n-var semver = require('semver');\n-\n-var ROOT_SPAN_STACK_OFFSET = semver.satisfies(process.version, '>=8') ? 0 : 2;\n+import * as uuid from 'uuid';\n \n /**\n- * Phantom implementation of the trace api. When disabled, a TraceAgent instance\n- * will have its public method implementations replaced with corresponding\n- * no-op implementations in this object.\n+ * An interface describing configuration fields read by the TraceAgent object.\n+ * This includes fields read by the trace policy.\n  */\n-var phantomApiImpl = {\n-  enhancedDatabaseReportingEnabled: function() { return false; },\n-  runInRootSpan: function(opts, fn) { return fn(null); },\n-  getCurrentContextId: function() { return null; },\n-  createChildSpan: function(opts) { return null; },\n-  getResponseTraceContext: function(context, traced) { return ''; },\n-  getWriterProjectId : function() { return null; },\n-  wrap: function(fn) { return fn; },\n-  wrapEmitter: function(ee) {},\n-};\n-\n-// A sentinal stored in CLS to indicate that the current request was not sampled.\n-var nullSpan = {};\n+export interface TraceAgentConfig extends TracingPolicy.TracePolicyConfig {\n+  enhancedDatabaseReporting: boolean;\n+  ignoreContextHeader: boolean;\n+  projectId?: string;\n+}\n \n /**\n- * TraceAgent exposes a number of methods to create trace spans and propagate\n- * trace context across asynchronous boundaries.\n- * @constructor\n- * @param {String} name A string identifying this TraceAgent instance in logs.\n+ * An interface that describes the available options for creating a span in\n+ * general.\n  */\n-function TraceAgent(name) {\n-  this.pluginName_ = name;\n-  this.disable(); // disable immediately\n+export interface SpanOptions {\n+  /* The name to apply to the span. */\n+  name: string;\n+  /**\n+   * The number of stack frames to skip when collecting call stack information\n+   * for the span, starting from the top; this should be set to avoid including\n+   * frames in the plugin. Defaults to 0.\n+   */\n+  skipFrames?: number;\n }\n \n /**\n- * Enables this instance. This function is only for internal use and\n- * unit tests. A separate TraceWriter instance should be initialized beforehand.\n- * @param {common.logger} logger A logger object.\n- * @param {Configuration} config An object specifying how this instance should\n- * be configured.\n- * @private\n+ * An interface that describes the available options for creating root spans.\n  */\n-TraceAgent.prototype.enable = function(logger, config) {\n-  this.logger_ = logger;\n-  this.config_ = config;\n-  this.policy_ = TracingPolicy.createTracePolicy(config);\n-  this.namespace_ = cls.getNamespace();\n-  for (var memberName in TraceAgent.prototype) {\n-    this[memberName] = TraceAgent.prototype[memberName];\n-  }\n-};\n+export interface RootSpanOptions extends SpanOptions {\n+  /* A URL associated with the root span, if applicable. */\n+  url?: string;\n+  /**\n+   * The serialized form of an object that contains information about an existing\n+   * trace context.\n+   */\n+  traceContext?: string;\n+}\n \n /**\n- * Disable this instance. This function is only for internal use and\n- * unit tests.\n- * @private\n+ * Type guard that returns whether an object is a string or not.\n  */\n-TraceAgent.prototype.disable = function() {\n-  // Even though plugins should be unpatched, setting a new policy that\n-  // never generates traces allows persisting wrapped methods (either because\n-  // they are already instantiated or the plugin doesn't unpatch them) to\n-  // short-circuit out of trace generation logic.\n-  this.policy_ = new TracingPolicy.TraceNonePolicy();\n-  this.namespace_ = null;\n-  for (var memberName in phantomApiImpl) {\n-    this[memberName] = phantomApiImpl[memberName];\n-  }\n-};\n+function isString(obj: any): obj is string {\n+  return is.string(obj);\n+}\n \n /**\n- * Returns whether the TraceAgent instance is active. This function is only for\n- * internal use and unit tests; under normal circumstances it will always return\n- * true.\n- * @private\n+ * Type guard that returns whether an object is a SpanData object or not.\n+ * \n+ * @param obj \n  */\n-TraceAgent.prototype.isActive = function() {\n-  return !!this.namespace_;\n-};\n+function isSpanData(obj: cls.RootContext): obj is SpanData {\n+  // The second condition ensures that obj is not nullSpan.\n+  return !!obj && !!(obj as SpanData).span;\n+}\n \n-/**\n- * Gets the value of enhancedDatabaseReporting in the trace agent's\n- * configuration object.\n- * @returns A boolean value indicating whether the trace agent was configured\n- * to have an enhanced level of reporting enabled.\n- */\n-TraceAgent.prototype.enhancedDatabaseReportingEnabled = function() {\n-  return this.config_.enhancedDatabaseReporting;\n-};\n+// A sentinal stored in CLS to indicate that the current request was not sampled.\n+const nullSpan = Object.freeze({});\n+\n+const ROOT_SPAN_STACK_OFFSET = semver.satisfies(process.version, '>=8') ? 0 : 2;\n \n /**\n- * Runs the given function in a root span corresponding to an incoming request,\n- * possibly passing it an object that exposes an interface for adding labels\n- * and closing the span.\n- * @param {object} options An object that specifies options for how the root\n- * span is created and propogated.\n- * @param {string} options.name The name to apply to the root span.\n- * @param {?string} options.url A URL associated with the root span, if\n- * applicable.\n- * @param {?string} options.traceContext The serialized form of an object that\n- * contains information about an existing trace context.\n- * @param {?number} options.skipFrames The number of stack frames to skip when\n- * collecting call stack information for the root span, starting from the top;\n- * this should be set to avoid including frames in the plugin. Defaults to 0.\n- * @param {function(?RootSpan)} fn A function that will be called exactly\n- * once. If the incoming request should be traced, a root span will be created,\n- * and this function will be called with a RootSpan object exposing functions\n- * operating on the root span; otherwise, it will be called with null as an\n- * argument.\n- * @returns The return value of calling fn.\n+ * TraceAgent exposes a number of methods to create trace spans and propagate\n+ * trace context across asynchronous boundaries.\n  */\n-TraceAgent.prototype.runInRootSpan = function(options, fn) {\n-  var that = this;\n-  // TODO validate options\n-  // Don't create a root span if the required namespace doesn't exist, or we\n-  // are already in a root span\n-  if (!this.namespace_) {\n-    this.logger_.warn(this.pluginName_ + ': CLS namespace not present; not ' +\n-      'running in root span.');\n-    return fn(null);\n-  }\n-  if (cls.getRootContext()) {\n-    this.logger_.warn(this.pluginName_ + ': Cannot create nested root spans.');\n-    return fn(null);\n+export class TraceAgent {\n+  public readonly constants = Constants;\n+  public readonly labels = TraceLabels;\n+\n+  private pluginName_: string;\n+  private logger_: Logger;\n+  private config_: TraceAgentConfig;\n+  // TODO(kjin): Make this private.\n+  public policy_: TracingPolicy.TracePolicy;\n+  private namespace_: cls.Namespace | null;\n+\n+  /**\n+   * Constructs a new TraceAgent instance.\n+   * @param name A string identifying this TraceAgent instance in logs.\n+   */\n+  constructor(name: string) {\n+    this.pluginName_ = name;\n+    this.disable(); // disable immediately\n   }\n \n-  return this.namespace_.runAndReturn(function() {\n-    // Attempt to read incoming trace context.\n-    var incomingTraceContext;\n-    if (is.string(options.traceContext) && !that.config_.ignoreContextHeader) {\n-      incomingTraceContext = util.parseContextFromHeader(options.traceContext);\n+  /**\n+   * Enables this instance. This function is only for internal use and\n+   * unit tests. A separate TraceWriter instance should be initialized beforehand.\n+   * @param logger A logger object.\n+   * @param config An object specifying how this instance should\n+   * be configured.\n+   * @private\n+   */\n+  enable(logger: Logger, config: TraceAgentConfig) {\n+    this.logger_ = logger;\n+    this.config_ = config;\n+    this.policy_ = TracingPolicy.createTracePolicy(config);\n+    this.namespace_ = cls.getNamespace();\n+  };\n+\n+  /**\n+   * Disable this instance. This function is only for internal use and\n+   * unit tests.\n+   * @private\n+   */\n+  disable() {\n+    // Even though plugins should be unpatched, setting a new policy that\n+    // never generates traces allows persisting wrapped methods (either because\n+    // they are already instantiated or the plugin doesn't unpatch them) to\n+    // short-circuit out of trace generation logic.\n+    this.policy_ = new TracingPolicy.TraceNonePolicy();\n+    this.namespace_ = null;\n+  };\n+\n+  /**\n+   * Returns whether the TraceAgent instance is active. This function is only for\n+   * internal use and unit tests; under normal circumstances it will always return\n+   * true.\n+   * @private\n+   */\n+  isActive(): boolean {\n+    return !!this.namespace_;\n+  };\n+\n+  /**\n+   * Gets the value of enhancedDatabaseReporting in the trace agent's\n+   * configuration object.\n+   * @returns A boolean value indicating whether the trace agent was configured\n+   * to have an enhanced level of reporting enabled.\n+   */\n+  enhancedDatabaseReportingEnabled(): boolean {\n+    if (this.config_) {\n+      return this.config_.enhancedDatabaseReporting;\n+    } else {\n+      return false;\n     }\n-    incomingTraceContext = incomingTraceContext || {};\n+  };\n \n-    // Consult the trace policy, and don't create a root span if the trace\n-    // policy disallows it.\n-    var locallyAllowed = that.policy_.shouldTrace(Date.now(), options.url || '');\n-    var remotelyAllowed = isNaN(incomingTraceContext.options) ||\n-      (incomingTraceContext.options & Constants.TRACE_OPTIONS_TRACE_ENABLED);\n-    if (!locallyAllowed || !remotelyAllowed) {\n-      cls.setRootContext(nullSpan);\n+  /**\n+   * Runs the given function in a root span corresponding to an incoming request,\n+   * possibly passing it an object that exposes an interface for adding labels\n+   * and closing the span.\n+   * @param options An object that specifies options for how the root\n+   * span is created and propogated.\n+   * @param fn A function that will be called exactly\n+   * once. If the incoming request should be traced, a root span will be created,\n+   * and this function will be called with a RootSpan object exposing functions\n+   * operating on the root span; otherwise, it will be called with null as an\n+   * argument.\n+   * @returns The return value of calling fn.\n+   */\n+  runInRootSpan<T>(options: RootSpanOptions, fn: (span: SpanData | null) => T): T {\n+    if (!this.isActive()) {\n       return fn(null);\n     }\n \n-    // Create a new root span, and invoke fn with it.\n-    var traceId = incomingTraceContext.traceId || (uuid.v4().split('-').join(''));\n-    var parentId = incomingTraceContext.spanId || '0';\n-    var rootContext = new SpanData(new Trace('', traceId), /* Trace object */\n-      options.name, /* Span name */\n-      parentId, /* Parent's span ID */\n-      true, /* Is root span */\n-      ROOT_SPAN_STACK_OFFSET + (options.skipFrames || 0));\n-    rootContext.span.kind = 'RPC_SERVER';\n-    cls.setRootContext(rootContext);\n-    return fn(rootContext);\n-  });\n-};\n+    // This is safe because isActive checks the value of this.namespace_.\n+    const namespace = this.namespace_ as cls.Namespace;\n+    // TODO validate options\n+    // Don't create a root span if we are already in a root span\n+    if (cls.getRootContext()) {\n+      this.logger_.warn(this.pluginName_ + ': Cannot create nested root spans.');\n+      return fn(null);\n+    }\n \n-/**\n- * Returns a unique identifier for the currently active context. This can be\n- * used to uniquely identify the current root span. If there is no current,\n- * context, or if we have lost context, this will return null. The structure and\n- * the length of the returned string should be treated opaquely - the only\n- * guarantee is that the value would unique for every root span.\n- * @returns {string} an id for the current context, or null if there is none\n- */\n-TraceAgent.prototype.getCurrentContextId = function() {\n-  const rootSpan = cls.getRootContext();\n-  if (!rootSpan || rootSpan === nullSpan) {\n-    return null;\n-  }\n-  // TODO: When converting this file to TS, remove this cast.\n-  return (rootSpan as SpanData).trace.traceId;\n-};\n+    return namespace.runAndReturn(() => {\n+      // Attempt to read incoming trace context.\n+      let incomingTraceContext: {\n+        traceId?: string,\n+        spanId?: string,\n+        options?: number\n+      } = {};\n+      if (isString(options.traceContext) && !this.config_.ignoreContextHeader) {\n+        const parsedContext = util.parseContextFromHeader(options.traceContext);\n+        if (parsedContext) {\n+          incomingTraceContext = parsedContext;\n+        }\n+      }\n \n-/**\n- * Returns the projectId that was either configured or auto-discovered by the\n- * TraceWriter. Note that the auto-discovery is done asynchronously, so this\n- * may return falsey until the projectId auto-discovery completes.\n- */\n-TraceAgent.prototype.getWriterProjectId = function() {\n-  return this.config_.projectId;\n-};\n+      // Consult the trace policy, and don't create a root span if the trace\n+      // policy disallows it.\n+      const locallyAllowed = this.policy_.shouldTrace(Date.now(), options.url || '');\n+      const remotelyAllowed = incomingTraceContext.options === undefined ||\n+        !!(incomingTraceContext.options & Constants.TRACE_OPTIONS_TRACE_ENABLED);\n+      if (!locallyAllowed || !remotelyAllowed) {\n+        cls.setRootContext(nullSpan);\n+        return fn(null);\n+      }\n \n-/**\n- * Creates and returns a new ChildSpan object nested within the root span. If\n- * there is no current RootSpan object, this function returns null.\n- * @param {object} options An object that specifies options for how the child\n- * span is created and propagated.\n- * @param {string} options.name The name to apply to the child span.\n- * @param {?number} options.skipFrames The number of stack frames to skip when\n- * collecting call stack information for the root span, starting from the top;\n- * this should be set to avoid including frames in the plugin. Defaults to 0.\n- * @returns A new ChildSpan object, or null if there is no active root span.\n- */\n-TraceAgent.prototype.createChildSpan = function(options) {\n-  // TODO: When this file is converted to TS, remove this cast.\n-  var rootSpan = cls.getRootContext() as SpanData | null;\n-  if (!rootSpan) {\n-    // Context was lost.\n-    this.logger_.warn(this.pluginName_ + ': Attempted to create child span ' +\n-      'without root');\n-    return null;\n-  } else if (rootSpan === nullSpan) {\n-    // Context wasn't lost, but there's no root span, indicating that this\n-    // request should not be traced.\n-    return null;\n-  } else {\n-    if (rootSpan.span.isClosed()) {\n-      this.logger_.warn(this.pluginName_ + ': creating child for an already closed span',\n-        options.name, rootSpan.span.name);\n+      // Create a new root span, and invoke fn with it.\n+      const traceId = incomingTraceContext.traceId || (uuid.v4().split('-').join(''));\n+      const parentId = incomingTraceContext.spanId || '0';\n+      const rootContext = new SpanData(new Trace('', traceId), /* Trace object */\n+        options.name, /* Span name */\n+        parentId, /* Parent's span ID */\n+        true, /* Is root span */\n+        ROOT_SPAN_STACK_OFFSET + (options.skipFrames || 0));\n+      rootContext.span.kind = 'RPC_SERVER';\n+      cls.setRootContext(rootContext);\n+      return fn(rootContext);\n+    });\n+  };\n+\n+  /**\n+   * Returns a unique identifier for the currently active context. This can be\n+   * used to uniquely identify the current root span. If there is no current,\n+   * context, or if we have lost context, this will return null. The structure and\n+   * the length of the returned string should be treated opaquely - the only\n+   * guarantee is that the value would unique for every root span.\n+   * @returns an id for the current context, or null if there is none\n+   */\n+  getCurrentContextId(): string | null {\n+    if (!this.isActive()) {\n+      return null;\n     }\n-    // Create a new child span and return it.\n-    options = options || {};\n-    var skipFrames = options.skipFrames ? options.skipFrames + 1 : 1;\n-    var childContext = new SpanData(rootSpan.trace, /* Trace object */\n-      options.name, /* Span name */\n-      rootSpan.span.spanId, /* Parent's span ID */\n-      false, /* Is root span */\n-      skipFrames); /* # of frames to skip in stack trace */\n-    return childContext;\n-  }\n-};\n \n-/**\n- * Generates a stringified trace context that should be set as the trace context\n- * header in a response to an incoming web request. This value is based on\n- * the trace context header value in the corresponding incoming request, as well\n- * as the result from the local trace policy on whether this request will be\n- * traced or not.\n- * @param {string} incomingTraceContext The trace context that was attached to\n- * the incoming web request, or null if the incoming request didn't have one.\n- * @param {boolean} isTraced Whether the incoming was traced. This is determined\n- * by the local tracing policy.\n- * @returns {string} If the response should contain the trace context within its\n- * header, the string to be set as this header's value. Otherwise, an empty\n- * string.\n- */\n-TraceAgent.prototype.getResponseTraceContext = function(\n-    incomingTraceContext, isTraced) {\n-  var traceContext = util.parseContextFromHeader(incomingTraceContext);\n-  if (!traceContext) {\n-    return '';\n-  }\n-  traceContext.options = (traceContext.options || 0) & isTraced;\n-  return util.generateTraceContext(traceContext);\n-};\n+    const rootSpan: cls.RootContext = cls.getRootContext();","path":"src/trace-api.ts","position":448,"original_position":448,"commit_id":"f110062707fec654ef1629d56f0d389c0da7cac0","original_commit_id":"f110062707fec654ef1629d56f0d389c0da7cac0","user":{"login":"ofrobots","id":79017,"avatar_url":"https://avatars1.githubusercontent.com/u/79017?v=4","gravatar_id":"","url":"https://api.github.com/users/ofrobots","html_url":"https://github.com/ofrobots","followers_url":"https://api.github.com/users/ofrobots/followers","following_url":"https://api.github.com/users/ofrobots/following{/other_user}","gists_url":"https://api.github.com/users/ofrobots/gists{/gist_id}","starred_url":"https://api.github.com/users/ofrobots/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ofrobots/subscriptions","organizations_url":"https://api.github.com/users/ofrobots/orgs","repos_url":"https://api.github.com/users/ofrobots/repos","events_url":"https://api.github.com/users/ofrobots/events{/privacy}","received_events_url":"https://api.github.com/users/ofrobots/received_events","type":"User","site_admin":false},"body":"is this type definition inferrable?","created_at":"2017-09-27T00:01:41Z","updated_at":"2017-09-27T00:07:04Z","html_url":"https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/567#discussion_r141216301","pull_request_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls/567","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls/comments/141216301"},"html":{"href":"https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/567#discussion_r141216301"},"pull_request":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls/567"}}},"pull_request":{"url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls/567","id":143241958,"html_url":"https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/567","diff_url":"https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/567.diff","patch_url":"https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/567.patch","issue_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/issues/567","number":567,"state":"open","locked":false,"title":"refactor: ts conversion of trace-agent","user":{"login":"kjin","id":2365820,"avatar_url":"https://avatars3.githubusercontent.com/u/2365820?v=4","gravatar_id":"","url":"https://api.github.com/users/kjin","html_url":"https://github.com/kjin","followers_url":"https://api.github.com/users/kjin/followers","following_url":"https://api.github.com/users/kjin/following{/other_user}","gists_url":"https://api.github.com/users/kjin/gists{/gist_id}","starred_url":"https://api.github.com/users/kjin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kjin/subscriptions","organizations_url":"https://api.github.com/users/kjin/orgs","repos_url":"https://api.github.com/users/kjin/repos","events_url":"https://api.github.com/users/kjin/events{/privacy}","received_events_url":"https://api.github.com/users/kjin/received_events","type":"User","site_admin":false},"body":"This change converts `trace-agent.ts` to TypeScript.\r\n\r\nA caveat -- this change removes switching between `TraceAgent.prototype` and `phantomApiImplementation`, instead opting for having all member functions on the `TraceAgent` class return default values if the Trace Agent is disabled. The reason for this is that ES6 class prototype members aren't iterable in the same way as before. While there might be a workaround for this, I figured that I might as well remove the switching mechanism altogether (it could be considered bad style).","created_at":"2017-09-26T20:59:55Z","updated_at":"2017-09-27T00:07:04Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1938b69b6f7fad30fab768b832800c9709a093ca","assignee":null,"assignees":[],"requested_reviewers":[{"login":"matthewloring","id":1485909,"avatar_url":"https://avatars3.githubusercontent.com/u/1485909?v=4","gravatar_id":"","url":"https://api.github.com/users/matthewloring","html_url":"https://github.com/matthewloring","followers_url":"https://api.github.com/users/matthewloring/followers","following_url":"https://api.github.com/users/matthewloring/following{/other_user}","gists_url":"https://api.github.com/users/matthewloring/gists{/gist_id}","starred_url":"https://api.github.com/users/matthewloring/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/matthewloring/subscriptions","organizations_url":"https://api.github.com/users/matthewloring/orgs","repos_url":"https://api.github.com/users/matthewloring/repos","events_url":"https://api.github.com/users/matthewloring/events{/privacy}","received_events_url":"https://api.github.com/users/matthewloring/received_events","type":"User","site_admin":false},{"login":"gaofanmichael","id":3772244,"avatar_url":"https://avatars0.githubusercontent.com/u/3772244?v=4","gravatar_id":"","url":"https://api.github.com/users/gaofanmichael","html_url":"https://github.com/gaofanmichael","followers_url":"https://api.github.com/users/gaofanmichael/followers","following_url":"https://api.github.com/users/gaofanmichael/following{/other_user}","gists_url":"https://api.github.com/users/gaofanmichael/gists{/gist_id}","starred_url":"https://api.github.com/users/gaofanmichael/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gaofanmichael/subscriptions","organizations_url":"https://api.github.com/users/gaofanmichael/orgs","repos_url":"https://api.github.com/users/gaofanmichael/repos","events_url":"https://api.github.com/users/gaofanmichael/events{/privacy}","received_events_url":"https://api.github.com/users/gaofanmichael/received_events","type":"User","site_admin":false},{"login":"DominicKramer","id":14253877,"avatar_url":"https://avatars3.githubusercontent.com/u/14253877?v=4","gravatar_id":"","url":"https://api.github.com/users/DominicKramer","html_url":"https://github.com/DominicKramer","followers_url":"https://api.github.com/users/DominicKramer/followers","following_url":"https://api.github.com/users/DominicKramer/following{/other_user}","gists_url":"https://api.github.com/users/DominicKramer/gists{/gist_id}","starred_url":"https://api.github.com/users/DominicKramer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DominicKramer/subscriptions","organizations_url":"https://api.github.com/users/DominicKramer/orgs","repos_url":"https://api.github.com/users/DominicKramer/repos","events_url":"https://api.github.com/users/DominicKramer/events{/privacy}","received_events_url":"https://api.github.com/users/DominicKramer/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls/567/commits","review_comments_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls/567/comments","review_comment_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/issues/567/comments","statuses_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/statuses/f110062707fec654ef1629d56f0d389c0da7cac0","head":{"label":"kjin:ts-agent","ref":"ts-agent","sha":"f110062707fec654ef1629d56f0d389c0da7cac0","user":{"login":"kjin","id":2365820,"avatar_url":"https://avatars3.githubusercontent.com/u/2365820?v=4","gravatar_id":"","url":"https://api.github.com/users/kjin","html_url":"https://github.com/kjin","followers_url":"https://api.github.com/users/kjin/followers","following_url":"https://api.github.com/users/kjin/following{/other_user}","gists_url":"https://api.github.com/users/kjin/gists{/gist_id}","starred_url":"https://api.github.com/users/kjin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kjin/subscriptions","organizations_url":"https://api.github.com/users/kjin/orgs","repos_url":"https://api.github.com/users/kjin/repos","events_url":"https://api.github.com/users/kjin/events{/privacy}","received_events_url":"https://api.github.com/users/kjin/received_events","type":"User","site_admin":false},"repo":{"id":68134606,"name":"cloud-trace-nodejs","full_name":"kjin/cloud-trace-nodejs","owner":{"login":"kjin","id":2365820,"avatar_url":"https://avatars3.githubusercontent.com/u/2365820?v=4","gravatar_id":"","url":"https://api.github.com/users/kjin","html_url":"https://github.com/kjin","followers_url":"https://api.github.com/users/kjin/followers","following_url":"https://api.github.com/users/kjin/following{/other_user}","gists_url":"https://api.github.com/users/kjin/gists{/gist_id}","starred_url":"https://api.github.com/users/kjin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kjin/subscriptions","organizations_url":"https://api.github.com/users/kjin/orgs","repos_url":"https://api.github.com/users/kjin/repos","events_url":"https://api.github.com/users/kjin/events{/privacy}","received_events_url":"https://api.github.com/users/kjin/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/kjin/cloud-trace-nodejs","description":"Experimental Node.js support for Google Cloud Trace","fork":true,"url":"https://api.github.com/repos/kjin/cloud-trace-nodejs","forks_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/forks","keys_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/teams","hooks_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/hooks","issue_events_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/issues/events{/number}","events_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/events","assignees_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/assignees{/user}","branches_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/branches{/branch}","tags_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/tags","blobs_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/git/refs{/sha}","trees_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/statuses/{sha}","languages_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/languages","stargazers_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/stargazers","contributors_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/contributors","subscribers_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/subscribers","subscription_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/subscription","commits_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/commits{/sha}","git_commits_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/git/commits{/sha}","comments_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/comments{/number}","issue_comment_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/issues/comments{/number}","contents_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/contents/{+path}","compare_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/merges","archive_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/downloads","issues_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/issues{/number}","pulls_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/pulls{/number}","milestones_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/milestones{/number}","notifications_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/labels{/name}","releases_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/releases{/id}","deployments_url":"https://api.github.com/repos/kjin/cloud-trace-nodejs/deployments","created_at":"2016-09-13T18:12:30Z","updated_at":"2016-09-13T18:12:32Z","pushed_at":"2017-09-26T23:14:14Z","git_url":"git://github.com/kjin/cloud-trace-nodejs.git","ssh_url":"git@github.com:kjin/cloud-trace-nodejs.git","clone_url":"https://github.com/kjin/cloud-trace-nodejs.git","svn_url":"https://github.com/kjin/cloud-trace-nodejs","homepage":"https://cloud.google.com/cloud-trace/","size":2283,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_projects":true,"has_downloads":false,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":5,"forks":0,"open_issues":5,"watchers":0,"default_branch":"master"}},"base":{"label":"GoogleCloudPlatform:master","ref":"master","sha":"50ad81155a3a6afe9d4a76ddb90dfd3cfc1ea1ca","user":{"login":"GoogleCloudPlatform","id":2810941,"avatar_url":"https://avatars0.githubusercontent.com/u/2810941?v=4","gravatar_id":"","url":"https://api.github.com/users/GoogleCloudPlatform","html_url":"https://github.com/GoogleCloudPlatform","followers_url":"https://api.github.com/users/GoogleCloudPlatform/followers","following_url":"https://api.github.com/users/GoogleCloudPlatform/following{/other_user}","gists_url":"https://api.github.com/users/GoogleCloudPlatform/gists{/gist_id}","starred_url":"https://api.github.com/users/GoogleCloudPlatform/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GoogleCloudPlatform/subscriptions","organizations_url":"https://api.github.com/users/GoogleCloudPlatform/orgs","repos_url":"https://api.github.com/users/GoogleCloudPlatform/repos","events_url":"https://api.github.com/users/GoogleCloudPlatform/events{/privacy}","received_events_url":"https://api.github.com/users/GoogleCloudPlatform/received_events","type":"Organization","site_admin":false},"repo":{"id":41170869,"name":"cloud-trace-nodejs","full_name":"GoogleCloudPlatform/cloud-trace-nodejs","owner":{"login":"GoogleCloudPlatform","id":2810941,"avatar_url":"https://avatars0.githubusercontent.com/u/2810941?v=4","gravatar_id":"","url":"https://api.github.com/users/GoogleCloudPlatform","html_url":"https://github.com/GoogleCloudPlatform","followers_url":"https://api.github.com/users/GoogleCloudPlatform/followers","following_url":"https://api.github.com/users/GoogleCloudPlatform/following{/other_user}","gists_url":"https://api.github.com/users/GoogleCloudPlatform/gists{/gist_id}","starred_url":"https://api.github.com/users/GoogleCloudPlatform/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GoogleCloudPlatform/subscriptions","organizations_url":"https://api.github.com/users/GoogleCloudPlatform/orgs","repos_url":"https://api.github.com/users/GoogleCloudPlatform/repos","events_url":"https://api.github.com/users/GoogleCloudPlatform/events{/privacy}","received_events_url":"https://api.github.com/users/GoogleCloudPlatform/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/GoogleCloudPlatform/cloud-trace-nodejs","description":"Node.js support for Google Cloud Trace","fork":false,"url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs","forks_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/forks","keys_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/teams","hooks_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/hooks","issue_events_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/issues/events{/number}","events_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/events","assignees_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/assignees{/user}","branches_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/branches{/branch}","tags_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/tags","blobs_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/git/refs{/sha}","trees_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/statuses/{sha}","languages_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/languages","stargazers_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/stargazers","contributors_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/contributors","subscribers_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/subscribers","subscription_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/subscription","commits_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/commits{/sha}","git_commits_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/git/commits{/sha}","comments_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/comments{/number}","issue_comment_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/issues/comments{/number}","contents_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/contents/{+path}","compare_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/merges","archive_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/downloads","issues_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/issues{/number}","pulls_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls{/number}","milestones_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/milestones{/number}","notifications_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/labels{/name}","releases_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/releases{/id}","deployments_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/deployments","created_at":"2015-08-21T18:23:09Z","updated_at":"2017-09-21T11:23:41Z","pushed_at":"2017-09-26T23:14:15Z","git_url":"git://github.com/GoogleCloudPlatform/cloud-trace-nodejs.git","ssh_url":"git@github.com:GoogleCloudPlatform/cloud-trace-nodejs.git","clone_url":"https://github.com/GoogleCloudPlatform/cloud-trace-nodejs.git","svn_url":"https://github.com/GoogleCloudPlatform/cloud-trace-nodejs","homepage":"https://cloud.google.com/cloud-trace/","size":1890,"stargazers_count":116,"watchers_count":116,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":false,"has_wiki":false,"has_pages":false,"forks_count":52,"mirror_url":null,"open_issues_count":12,"forks":52,"open_issues":12,"watchers":116,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls/567"},"html":{"href":"https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/567"},"issue":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/issues/567"},"comments":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/issues/567/comments"},"review_comments":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls/567/comments"},"review_comment":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/pulls/567/commits"},"statuses":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-trace-nodejs/statuses/f110062707fec654ef1629d56f0d389c0da7cac0"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"ofrobots"}}