Logging Interface Access

I hope this message finds you well.

I would like to log the event of an interface being opened.
However, my understanding is that on the interface itself, unless an action such as clicking a button occurs,

it is not possible to write a record or start a process model.

When consulting ChatGPT, I received the following idea:

  1. Create a Web API on Appian that returns an image as the response and simultaneously saves a log.
  2. Place an image on the target interface and set the endpoint of the Web API created in (1) as the image source.
  3. As a result, when the interface is opened, the Web API in (1) is called, and the log is recorded.

However, it seems that the function a!writeToDataStoreEntity does not get triggered inside the Web API.

I have two questions and would appreciate your advice:

  1. What should I review to implement the above ChatGPT idea successfully?
  2. Are there alternative methods to log when an interface is opened?

Thank you in advance for your kind advice.

  Discussion posts and replies are publicly visible

Parents
  • Thank you all for your advice.

    Although I initially lacked some information, ultimately, I would like to calculate usage rates for each application from the log data. Therefore, I prefer to save the logs as records rather than text files. Additionally, since I need to calculate usage rates by organization, user information is also necessary.

    Hence, I believe the approach Mike has already implemented is the closest to what I need.

    I implemented the Web API as shown below. While it returns an image file as the response, it seems that neither a!startProcess, a!writeToDataStoreEntity, nor a!writeRecords functions are executing.

    I implemented it using the GET method to return the image file. Is there anything else I should do?


    a!localVariables(

      local!codea: 200,
      local!codeb: 200,
      local!codec: 200,

      local!a: a!writeToDataStoreEntity(
        dataStoreEntity: cons!XLOG_EVENT,
        valueToStore: 'type!{urn:com:appian:types:XLOG}XLOG_RT_EVENT'(
          timestamp: now(),
          appName: "Application Name",
          functionName: "Function Name",
          user: tostring(loggedInUser())
        ),
        onSuccess: a!save(local!codea, 201),
        onError: a!save(local!codea, 202)
      ),

      local!b: a!writeRecords(
        records: 'recordType!{}XLOG rtEvent'(
          'recordType!{}XLOG rtEvent.fields.{}timestamp': now(),
          'recordType!{}XLOG rtEvent.fields.{}appName': "Application Name RT",
          'recordType!{}XLOG rtEvent.fields.{}functionName': "Function Name RT",
          'recordType!{}XLOG rtEvent.fields.{}user': tostring(loggedInUser())
        ),
        onSuccess: a!save(local!codeb, 201),
        onError: a!save(local!codeb, 202)
      ),

      local!c: a!startProcess(
        processModel: cons!XLOG_pmTest,
        onSuccess: a!save(local!codec, 201),
        onError: a!save(local!codec, 202)
      ),

      local!d: a!richTextDisplayField(
        value: "code: " & local!codea & " " & local!codeb& " " & local!codec
      ),

      a!httpResponse(
        statusCode: local!codec,
        headers: {
          a!httpHeader(name: "Content-Type", value: "image/png"),
          a!httpHeader(name: "Cache-Control", value: "no-store, no-cache, must-revalidate, max-age=0"),
          a!httpHeader(name: "Pragma", value: "no-cache")
        },
        body: cons!XLOG_PNG
      )
    )

    NOTE:In this sample code, these local variables are only for debbuging.



  • 0
    Certified Lead Developer
    in reply to Keizo Watsuji

    This isn't really how to structure what I was suggesting.  My setup has 5 main layers - it could potentially be condensed some, but the layer structure defined here gives you levels of abstraction that will increase potential reusability and reduce rework efforts should you decide to change the way something works in the future.

    1. On your interface, call an expression rule in a Local Variable definition, passing in the person's username, and relevant details about where they're viewing from.
    2. In the expression rule, call an Integration you set up, and map the results.  It doesn't need to do anything as complex as returning an "image file", it could simply return a timestamp or boolean value.  My suggestion would be simply having it return a "last updated" timestamp (to be stored in the aforementioned Local Variable).
    3. In the integration, call the Web API you set up, passing in the details sent in from the Expression Rule.  Tie it to a Connected System pointing at your own environment and with a Service Account that has permissions to launch the process model used in the Web API.
    4. in the Web API, configure as a POST method and call the designated Process Model using a!startProcess(), passing in the same relevant details having been passed down.
    5. In the process model, you can do your DB write / update, passing back values to the caller indicating that it ran successfully (such as a "last updated" timestamp value).  If you decide to look up an existing row for the user in question (such as grouping rows by day, so as not to clutter the table with a row for every single refresh), it will be trivially easy, for instance, to pre-query any relevant row for the user per location/day, and if one exists, update that row, and otherwise, create a new one.  Or just write a new row every time, that part is up to you.
Reply
  • 0
    Certified Lead Developer
    in reply to Keizo Watsuji

    This isn't really how to structure what I was suggesting.  My setup has 5 main layers - it could potentially be condensed some, but the layer structure defined here gives you levels of abstraction that will increase potential reusability and reduce rework efforts should you decide to change the way something works in the future.

    1. On your interface, call an expression rule in a Local Variable definition, passing in the person's username, and relevant details about where they're viewing from.
    2. In the expression rule, call an Integration you set up, and map the results.  It doesn't need to do anything as complex as returning an "image file", it could simply return a timestamp or boolean value.  My suggestion would be simply having it return a "last updated" timestamp (to be stored in the aforementioned Local Variable).
    3. In the integration, call the Web API you set up, passing in the details sent in from the Expression Rule.  Tie it to a Connected System pointing at your own environment and with a Service Account that has permissions to launch the process model used in the Web API.
    4. in the Web API, configure as a POST method and call the designated Process Model using a!startProcess(), passing in the same relevant details having been passed down.
    5. In the process model, you can do your DB write / update, passing back values to the caller indicating that it ran successfully (such as a "last updated" timestamp value).  If you decide to look up an existing row for the user in question (such as grouping rows by day, so as not to clutter the table with a row for every single refresh), it will be trivially easy, for instance, to pre-query any relevant row for the user per location/day, and if one exists, update that row, and otherwise, create a new one.  Or just write a new row every time, that part is up to you.
Children
No Data