Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debugging APIs #30

Open
1 task
hjdivad opened this issue Jul 26, 2022 · 3 comments
Open
1 task

Debugging APIs #30

hjdivad opened this issue Jul 26, 2022 · 3 comments
Labels
enhancement New feature or request

Comments

@hjdivad
Copy link
Contributor

hjdivad commented Jul 26, 2022

Create Debugging APIs

Tasks:

  • spike trampoline-debugging package
    • thoughts on name?

We expect $debug APIs to continuously evolve with user feedback. The high order bit for our initial release is

  1. have the foundations built for ergonomic trampoline debugging
  2. have some initial useful debugging APIs

Why

Current best-in-breed debugging involves adding code that is stripped for production builds (e.g. import.meta.dev or ember's @glimmer/env). The obvious problem here is that valuable debugging code isn't available when debugging production.

We want to eat our cake 🍰 and have it too -- rich debugging APIs, available in prod, without bloating application size.

Principles

We'll write (build or buy) a node-debug style library for logging + conveniently adding breakpoints. It'll be globally available via something like $debug and individual packages will create scoped instances, similar to node-debug.

All other debug APIs are accessible from a $debug property.

In production builds, $debug's contents are moved to a lazily loaded ES module. On first access, the user is told how to do the loading. Exact details TBD, but something like $debug prints a message and returns a function that does the lazy loading.

In dev and test $debug will behave the same, except that it will autoload if we detect that devtools is open.

It is important that PRs are red (i.e. unmergable) if they use $debug, but that $debug should otherwise be available even in CI. Exact details TBD, but there are a number of techniques (e.g. injecting a failing test).

Initial Guidance

Until trampoline-debug exists, write code like the following:

function hasDebug() { return true; }

class MyClass {
  get $debug() {
    return { log: console.log };
  }

  myMethod() {
    if (hasDebug()) {
      $debug.log('scope', ...debugInfo)
    }
  }
}

Sample Userland APIs

import $debug from 'trampoline-debug';

// node-debug style matching
$debug.match = 'athena:*';
// packages can add their own scopes, e.g. athena:debug:*, athena:info:*
$debug.match = ['athena:*', 'cache:get:*']
$debug.match((debugPath): boolean);

$debug.breakOn('athena:query:*');
$debug.breakOn('athena:mutation:request');
$debug.breakOn('cache:get:*');
$debug.breakOn((debugPath, debugEvent) : boolean);

// equivalent to $debug.match('athena:mutation:*');
athena.$debug.match('mutation:*');
// equivalent to $debug.breakOn('athena:query:*');
athena.$debug.breakOn('query:*')
// equivalent to $debug.match('cache:tx:commit:*');
cache.$debug.match('tx:commit:*');
// equivalent to $debug.breakOn('cache:get:*');
cache.$debug.breakOn('get:*')


athena.$debug.printSchema()
athena.$debug.openGraphiQL() // open graphiql
// prints a table of all operations athena has executed
// include:
//  * queryId
//  * query name
//  * query contents
//  * variables (names & values)
//  * response body
//  * stack trace to execute{Query, Mutation}
//  * count of new entities
//  * count of merged entities
//  * timings
//  * path to query + open options
//    - in dev tools
//    - in graphiql
//    - in editor
//
athena.$debug.printOperations()
// print detailed information for each execution of a specific operation
// similar to printOperations
athena.$debug.operations.find((q) => true).print()
// with caputring enabled, on data property access, capture the render stack (ui integrations, ember, react &c.)
// then highlight them similar to devtools element hovering
athena.$debug.captureRenderStacks();
athena.$debug.queries.find((q) => true).render.highlight()

// print a table of revisions of the cache entry with <key>, alongside the source (e.g. operation, or manual transaction)
athena.cache.$debug.history(key)
// as above, but look up the cache entry by value instead of key
athena.cache.$debug.history(value)
// as above, but return the data rather than printing to the console
athena.cache.$debug.history(key, { silent: true })

Sample Library APIs

import { hasDebug, buildExtendedDebug, noDebugHelper } from 'trampoline-debug';

class Athena {
  get $debug() {
    if(hasDebug()) {
      return buildExtendedDebug('athena', this, {
        printSchema() {},
        openGraphiQL() {},
      });
    } else {
      return noDebugHelper();
    }
  },

  executeQuery() {
    if(hasDebug()) {
      // athena:query:request
      this.$debug.log('query:request', queryId, queryString, ...otherDebugInfo)

      await request();

      // athena:query:response
      this.$debug.log('query:response', queryId, queryString, ...otherDebugInfo)
    }
  }
}


import { hasDebug, buildExtendedDebug, noDebugHelper } from 'trampoline-debug';

class Cache {
  get $debug() {
    if(hasDebug()) {
      return buildExtendedDebug('cache', this, {
        history() {},
      });
    } else {
      return noDebugHelper();
    }
  },

  get(key) {
    if(hasDebug()) {
      // cache:get:${key}
      this.$debug.log(`get:${key}`, ...otherDebugInfo)
    }

    // ...
  }
}

// some class without customizations
import { hasDebug, buildDebug, noDebugHelper } from 'trampoline-debug';

const $debug = buildDebug('my-class');

class MyClass {
  method(key) {
    if(hasDebug()) {
      // my-class:foo
      $debug.log('foo', ...someDebugInfo)
    }

    // ...
  }
}

// some-other-module
import { hasDebug, buildDebug, noDebugHelper } from 'trampoline-debug';

const $debug = buildDebug('my-module');

export function moduleFn() {
  if(hasDebug()) {
    $debug.log('scope', ...debugInfo)
  }
}
hjdivad added a commit that referenced this issue Jul 26, 2022
@hjdivad
Copy link
Contributor Author

hjdivad commented Jul 26, 2022

@gabrielcsapo @shuba3862 thoughts?

@shuba3862
Copy link
Contributor

shuba3862 commented Jul 28, 2022

Overall LGTM

I understand we also need to define buildDebug interface that will be one of the key interface debug library should support?

Could you help clarify what does "// some class without customizations" mean here? Does that mean we have class Athena that customizes how breakpoints are set and the other just uses default debug setup?

@shuba3862
Copy link
Contributor

I like trampoline-debugging translates to the gymnastics of code and developer while debugging :)

@gabrielcsapo gabrielcsapo added the enhancement New feature or request label Aug 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants