dev: add a feature to hook tracing into context

This commit is contained in:
KernelDeimos 2024-12-11 16:02:45 -05:00
parent c2b094a351
commit 08dbef7b8c
3 changed files with 70 additions and 3 deletions

View File

@ -0,0 +1,21 @@
const BaseService = require("../../services/BaseService");
const { Context } = require("../../util/context");
/**
* ContextService provides a way for other services to register a hook to be
* called when a context/subcontext is created.
*
* Contexts are used to provide contextual information in the execution
* context (dynamic scope). They can also be used to identify a "span";
* a span is a labelled frame of execution that can be used to track
* performance, errors, and other metrics.
*/
class ContextService extends BaseService {
register_context_hook (event, hook) {
Context.context_hooks_[event].push(hook);
}
}
module.exports = {
ContextService,
};

View File

@ -50,6 +50,9 @@ class Core2Module extends AdvancedBase {
const { ParameterService } = require("./ParameterService.js");
services.registerService('params', ParameterService);
const { ContextService } = require('./ContextService.js');
services.registerService('context', ContextService);
}
}

View File

@ -23,6 +23,13 @@ class Context {
static USE_NAME_FALLBACK = {};
static next_name_ = 0;
static other_next_names_ = {};
// Context hooks should be registered via service (ContextService.js)
static context_hooks_ = {
pre_create: [],
post_create: [],
pre_arun: [],
};
static contextAsyncLocalStorage = new AsyncLocalStorage();
static __last_context_key = 0;
@ -59,8 +66,8 @@ class Context {
static describe () {
return this.get().describe();
}
static arun (cb) {
return this.get().arun(cb);
static arun (...a) {
return this.get().arun(...a);
}
static sub (values, opt_name) {
return this.get().sub(values, opt_name);
@ -72,6 +79,14 @@ class Context {
this.values_[k] = v;
}
sub (values, opt_name) {
if ( typeof values === 'string' ) {
opt_name = values;
values = {};
}
const name = opt_name ?? this.name ?? this.get('name');
for ( const hook of this.constructor.context_hooks_.pre_create ) {
hook({ values, name });
}
return new Context(values, this, opt_name);
}
get values () {
@ -99,6 +114,7 @@ class Context {
opt_parent = opt_parent || Context.root;
this.trace_name = opt_name ?? undefined;
this.name = (() => {
if ( opt_name === this.constructor.USE_NAME_FALLBACK ) {
opt_name = 'F';
@ -129,7 +145,34 @@ class Context {
this.values_ = values;
}
async arun (cb) {
async arun (...args) {
let cb = args.shift();
let hints = {};
if ( typeof cb === 'object' ) {
hints = cb;
cb = args.shift();
}
if ( typeof cb === 'string' ) {
const sub_context = this.sub(cb);
return await sub_context.arun({ trace: true }, ...args);
}
const replace_callback = new_cb => {
cb = new_cb;
}
for ( const hook of this.constructor.context_hooks_.pre_arun ) {
hook({
hints,
name: this.name ?? this.get('name'),
trace_name: this.trace_name,
replace_callback,
callback: cb,
});
}
const als = this.constructor.contextAsyncLocalStorage;
return await als.run(new Map(), async () => {
als.getStore().set('context', this);