Question
· Nov 6, 2017

Logging

Hello, evrybody, I'm writing one project using CSP("OnPreHTTP"), and also REST angular. At the beginning I wrote purely on csp, then I decided to use angular for the flexibility of the client part. Now I can not design logging, I created a table App.Log with properties

/// Type of event
Property EventType As %String(MAXLEN = 10, VALUELIST = ",NONE,FATAL,ERROR,WARN,INFO,STAT,DEBUG,RAW") [ InitialExpression = "INFO" ];

/// Name of class, where event happened
Property ClassName As %String(MAXLEN = 256);

/// Name of method, where event happened
Property MethodName As %String(MAXLEN = 128);

/// Line of int code
Property Source As %String(MAXLEN = 2000);

/// Line of cls code
Property SourceCLS As %String(MAXLEN = 2000);

/// User
Property Person As Data.Person;

/// Arguments' values passed to method
Property Arguments As %String(MAXLEN = 32000, TRUNCATE = 1);

/// Date and time
Property TimeStamp As %TimeStamp [ InitialExpression = {$zdt($h, 3, 1)} ];

/// User message
Property Message As %String(MAXLEN = 32000, TRUNCATE = 1);

/// User IP address
Property ClientIPAddress As %String(MAXLEN = 32) [ InitialExpression = {..GetClientAddress()} ];

But I can not competently organize it. I have not worked on authorization yet, because of this, there are no headers in the REST services, and when authorizing, I just write "PersonId" in the session(OnPreHttp). Is it possible to know the user through the session, in the broker, or in the triggers? And, in general, is it possible to organize logging in my case?

Discussion (3)0
Log in or sign up to continue

No, you can't. Unless you ovewrite some methods from %CSP.REST, like DispatchMethod.
 

Even so, simply ovewritting it wouldn't give you data about arguments that your dispatch method received, you need to go further than that and modify the way the info about the route is generated.

I also had headaches with that, because the pattern generation algorithm simply replaces any :placeholder by it's regular expression group counterpart, e.g. for Route elements /:msg becomes /([^/]+), but there're some variations according to which element you use to define it: Map or Route.

When compile your class, a method generator runs and creates a DispatchMap that takes an index, this index contains info about your dispatch method, http method and pattern to match. This index is based on the order you defined each element.

However DispatchMap does not generate info about the name of the parameter affected, only their index, so you would need to parse it by opening the dispatch method's class %Dictionary.CompiledMethod and accessing each Property's FormalSpec and matching their index to find the name of the argument.

This is some of the reasons why I implemented this, because I found %CSP.REST very limited. And what you want is something that I call: Reporter. It's a an abstract module that runs custom implementations whenever a method ends abnormally. I already implemented a sample of it for reporting an error via e-mail. 

EDIT: I just noticed that you want something to run for every request, regardless of success or error. I'll work on it.

You must notice two methods as declared here. These are the methods that should be implemented.  After that you only need to register the setup class with your router class. If you want to see it quickly, just change the commented reporter to this one and configure your e-mail parameters.

Finally, you can use %OAuth2's JWT implementation to store info about your current user. I'd recommend you to read this tutorial to learn how to setup it.

And if you want to read more about how my implementation works, you can find it out here.

It actually works and it can even transport objects using %session.Data, it also seems to run in a separate process from the request, which means a non-blocking operation. But I could  only make it work by defining the event class on the web application config even though there's a way to define it dynamically by using %session.EventClass.

So my only question is: Is there a moment for setting this up? Like when using OnPreHTTP for %response? I tried setting it using the classmethod Page after calling the superclass (%CSP.REST) but no avail. And %CSP.REST doesn't provide OnPreHTTP because it overwrites the Page method completely.

Now regarding the data we can retrieve from the default %CSP.REST implementation is none.  As we only have access to %session and %request (I didn't tried %response), it lacks any metadata that is used for %CSP.REST based applications (dispatch method, dispatch class, route arguments, stack, etc) since no object is created for it.

 

That means you still need a way to retrieve this info, this is why I had to transport the object over by using %session.Data. And here's how I did it:

ClassMethod Page(skipheader As %Boolean = 1) As %Status [ ProcedureBlock = 0 ]
{
  new %frontier
  set %frontier = ##class(Frontier.Context).%New(%session, %request, %response)
  set %session.Data("%frontier") = %frontier
  $$$QuitOnError(##super(skipheader))
  return $$$OK
}

And the event class:

/// Called when we have finished processing this request
ClassMethod OnEndRequest() As %Status
{
  set frontier = %session.Data("%frontier")
  return frontier.ReporterManager.Report()
}

Now using %CSP.REST I think the best approach is to create an utility method that is called on the beginning of each dispatch method. This utility would populate %session.Data with context sensitive info like arguments received. This method must be called for each method that is dispatchable. An example:

ClassMethod PopulateLogObject(arguments As %List, method, classname)
{
    set log = ##class(MyApp.Log).%New()
    set log.username = $username // <--  This is the only one you can fetch directly when using %CSP.SessionEvents.
    set log.parameters = arguments
    set log.method = method
    set log.classname = $classname()
    return log.%Save()
}

ClassMethod DoSomethingRequestedOverHTTP(argA, argB, argC)
{
     try {
        $$$ThrowOnError(..PopulateLogObject($lb(argA, argB, argC), "DoSomethingRequestedOverHTTP", $classname())

     } catch e {
        // render error response.
     }
    // render success response.
}

I don't know of any other possible way from getting this kind of info, unless if you overwrite DispatchRequest from %CSP.REST.