0

I'd like to enhance an Interceptor in my LoopBack4 application, which currently simply prints me the start and end of a controller method call on the command line - as described here: https://loopback.io/doc/en/lb4/Interceptors.html

My Log-Interceptor looks like this:

export const Log: Interceptor = async (invocationCtx, next) => {
    // Wait until the interceptor/method chain returns
    const req = await invocationCtx.get(RestBindings.Http.REQUEST);

    try
    {
        const stackinfo = 'Class: ' + invocationCtx.targetClass.name + ' | Method: ' + invocationCtx.methodName + " | Request IPs: " + req.ips.concat(', ');

        logger.trace('Starting - ' + stackinfo);

        const result = await next();
        const res = await invocationCtx.get(RestBindings.Http.RESPONSE);

        logger.trace('Ending - ' + stackinfo + ' | Response Status Code: ' + res.statusCode);

        return result;
    }
    catch (e)
    {
        logger.error(e);
        throw e;
    }
};

Now i'd like to enhance this Interceptor to also log some statistics data into my MySQL-Datasource. My problem is, how can i access the repository inside the interceptor? Do i have to inject the Repository and if yes, how do i have to do that? Or is there a better way to achieve this?

Timofey Stolbov
  • 4,501
  • 3
  • 40
  • 45
MIB
  • 337
  • 2
  • 15

2 Answers2

1

You can access repositories in your interceptors through invocationCtx.target like so:

export const exampleInterceptor: Interceptor = async (invocationCtx, next) => {
  const exampleRepo = invocationCtx.target.exampleRepository;
  const anotherExampleRepo = invocationCtx.target.anotherExampleRepository;
};

given that you have corresponding @repository decorators in the controller where your interceptor is going to be (works with both class level and method level interceptors)

@intercept(exampleInterceptor)
export class ExampleController {
  constructor(
    @repository(ExampleRepository)
    public exampleRepository: ExampleRepository,
    @repository(AnotherExampleRepository)
    public anotherExampleRepository: AnotherExampleRepository
  ) {}
}

Not sure if this is the recommended solution. Would love to hear other suggestions.

David
  • 11
  • 1
0

I found a solution on my own:

  1. create a service:
export class StatisticService
{
    constructor(
        @repository(StatisticRecordRepository) public statisticsRepository: StatisticRecordRepository
    ) {}


    async increment(key: string, addend = 1): Promise<void>
    {
        const existing = await this.statisticsRepository.findOne({where: {StatsKey: key}});
        if(existing !== null)
        {
            // @ts-ignore
            existing.Counter = existing.Counter + addend;
            existing.UpdateTs = (new Date()).toISOString();
            await this.statisticsRepository.update(existing);
        }
        else
        {
            await this.statisticsRepository.create(new StatisticRecord({
                                                                           StatsKey: key,
                                                                           Counter:  addend
                                                                       }));
        }
    }
}


export const StatisticsServiceBindings = {
    VALUE: BindingKey.create<StatisticService>("services.StatisticsService")
};
  1. bind the service in the Application constructor:
this.bind(StatisticsServiceBindings.VALUE).toClass(StatisticService);
  1. get and use the Service in the Log-Interceptor:
const stats = await invocationCtx.get(StatisticsServiceBindings.VALUE);
stats.increment(invocationCtx.targetClass.name + '::' + invocationCtx.methodName + ' [' + res.statusCode + ']');
MIB
  • 337
  • 2
  • 15