Scripted Fields for Billing Meters

What Are Scripted Fields?

If a field does not exist in your request or response that you’d like to bill upon, you can create the field using a script. The script retroactively creates a custom field from other fields, using formulas, arithmetic, and conditional expressions. A scripted field has the follow capabilities and limitations:

  • Only numbers, booleans, and datetime data types (treated as a number, milliseconds since epoch) are supported.
  • [field.path.field_name] references a field.
  • If field value does not exist, for numbers it defaults to 0, and for dates it defaults to the epoch. You can use [field.path.field_name|50] to set the default value.
  • Most mathematical functions are supported. Conditional expressions using if, else, then, and end operators are supported.
  • Only expressions are supported. No other variables or statements are supported. Scripted fields with body fields may take several minutes to query.

Creating a Scripted Field

To add a scripted field, follow these steps:

  1. Select Event Count and then select { } Select Field. Opening the Metrics dropdown menu in a Billing Meters Metrics pane
  2. Select <> Scripted Field from the Select a field list.
  3. Write the script and then select Set. The field list following the script text box contains all the existing fields so that you can easily reference them in your script. For more information on how to write scripts, see Using the Scripting Language.

    scripted field modal in Moesif

Using the Scripting Language

When using Scripted Fields, you create the new field by using MoesifFieldScript. MoesifFieldScript, also referred to as MSF, is a domain-specific language designed to perform custom computations on field values from events within the Moesif platform. This language provides the ability to evaluate logical, comparison, arithmetic, and functional expressions on event fields. Additionally, it supports accessing specific keys from the event data and provides default values if the specified key is not found.

Supported Constants, Operators, and Functions

MoesifFieldScript supports a variety of different mathematical constants, operators, and functions that can be used to create a value for a custom field. Below is list of currently supported entities.

Constants

type values
Numeric Constants Any whole number or decimal like 123, 4.56
Boolean Constants true or false

Operators

Arithmetic Operators:
symbol operator name/function
+ Addition
- Subtraction
* Multiplication
/ Division
Comparison Operators:
symbol operator name/function
== Equals
!= Not Equals
< Less Than
<= Less Than or Equal
> Greater Than
>= Greater Than or Equal
Logical Operators:
symbol operator name/function
and Logical AND
or Logical OR

Functions

Numeric Functions

symbol function name/functionality
round Rounds a number
ceil Rounds a number up
floor Rounds a number down
abs Absolute value of a number
min Minimum of numbers
max Maximum of numbers
avg Average of numbers
exp Exponential function
log Natural logarithm
pow Power of a number
sqrt Square root

String Functions

symbol function name/functionality
countSubstrings Count of substrings in a string

Examples

Below are some examples of how these operators and functions can be used within a MSF expression.

  1. To check if the value in a field, such as age, is less than 18:

     [age] < 18
    
  2. To sum the value of two fields, such as price and tax:

     [price] + [tax]
    
  3. To get the maximum of two fields, such as score1 and score2:

     max([score1], [score2])
    
  4. As a conditional expression, such as to check if status is active and return 1, otherwise 0:

     if [status] == "active" then 1 else 0 end
    

Referencing One Or More Fields in The Event

In Moesif, every API event is structured as an event document with various fields, including details about the request, response, and other associated metadata. When writing expressions in MoesifFieldScript, you can access these fields using a key path. This key path is a flattened representation of the JSON structure that omits array indices and focuses only on the names of JSON keys.

The schema of the event document is the same as what is provided in the event collector API. For more information on the schema, check out the API Call docs

To access specific values from the event data in MoesifFieldScript, you can use the following ways to access the value within a specific field using the fields key:

  • [keyName]: Accesses the value of the key keyName.
  • [key1.key2]: Accesses nested keys.
  • [keyName|defaultValue]: Accesses the key keyName and if not found, uses defaultValue.

Note: Special characters in key names should be escaped with a backslash (e.g. \\[ or \\\\).

Accessing Top-Level Fields

In Moesif, certain fields at the top level (those not contained in the request or response themselves), can also be accessed. For top-level fields in the event schema, such as time, user_id, and direction, MoesifFieldScript can access them directly by using their respective names. Below are two examples of such fields:

  • [time]: Accesses the timestamp of the request.
  • [user_id]: Accesses the associated user of the API call.

Accessing Request and Response Body Fields

Depending on how Moesif is configured, you may be able to use request and response body fields within your Scripted Field. For fields inside the body of the request and response objects, you can use the flattened JSON key path to access them. For example, consider an API event with the following request body:

{
  "request": {
    "body": {
      "fields": {
        "name": "John",
        "address": {
          "city": "San Francisco",
          "state": "CA"
        }
      }
    }
  }
}

To access the name field in MoesifFieldScript, you could use the following:

[request.body.fields.name]

To access the city inside the address object, you could use the following:

[request.body.fields.address.city]

Accessing Fields Within an Array

If there are arrays within the JSON structure, the flattened key path omits array indices. Instead, it focuses on accessing the nested keys directly. This simplifies access to fields and ensures consistency in expression writing. For example, Consider an API event with this request body:

{
  "request": {
    "body": {
      "fields": {
        "users": [
          {"id": 1, "name": "John"},
          {"id": 2, "name": "Jane"}
        ]
      }
    }
  }
}

To access the name of the second user, you would not use an array index. Instead, you’d access it as:

[request.body.fields.users.name]

It’s important to note that without array indices, this method can only access fields when the structure is known and consistent. If you need to access specific array elements, additional methods or processing might be required outside of MoesifFieldScript

Working With Dates

In MoesifFieldScript, date values are represented as the number of milliseconds that have elapsed since the Unix epoch (January 1, 1970, 00:00:00 GMT). This numeric representation allows for easy date comparison and arithmetic operations. Below are some examples of how dates can be used within a Scripted Field.

Checking If a Date is Before a Specific Time

To check if the date in the field fields.example.date is before January 2, 1970, 00:00:01 GMT (the Unix Epoch) or the value 1000, the timestamp for this specific time in milliseconds since epoch is 1000, the expression would be:

[fields.example.date] < 1000

Calculating The Difference Between Two Date Fields

If you have two date fields you’d like to find the difference between, you can simply use an equation in the expression to do so. For example, to find the difference in milliseconds between startDate and endDate fields, the expression would be:

[endDate] - [startDate]

Checking If an Event Occurred Within a Given Time Period

To return a boolean value of whether an event occurred within a given time period, you can calculate the difference between the current time and the event date and see if it is less than the time period you are concerned with. For example, given that there are 86,400,000 milliseconds in a day, to check if an event with the field eventDate occurred in the last 24 hours you could use the following expression:

[currentTime] - [eventDate] <= 86400000

Where currentTime is the current timestamp.

Adding Days/Time to a Date:

It is also possible to add days and time to a given date. For example, to add 7 days to a date value in the eventDate field, you can simply add 604800000 (the amount of milliseconds in one week) to the current value. That expression would lok like so:

[eventDate] + 604800000

Rounding Down to The Nearest Day:

If you want to ignore the time component and just consider the date, you can use the following expression to divide the timestamp by the number of milliseconds in a day, round down to get the number of days since the epoch, and then multiply the result by the number of milliseconds in a day to get the timestamp for the start of the day.

floor([eventDate] / 86400000) * 86400000

Working With Strings

Counting Substrings in a String:

If you want to count how many times a substring occurs in a string field, you can use countSubstrings().

countSubstrings([response.body.name|""], "AVG")

Support for Conditionals

In the scripted field, you will be able to add in conditionals, such as an if-else statement. Below are a few examples of how conditionals can be used

As a traditional, multi-line if-else statement:

if [request.query.billable] != true
then
1
else
2 * [response.body.usage]
end

or in a single-line statement:

if [field.value] == 42 then 2.2 * 31 else if [field.value] > 42 then  1.7 * 31 else 1.2 * 31 end end

Boolean operator’s can also be used within the conditional statement:

if [field.value] < 42 and [field.value] != 0 then 1 + 2 else 2 * 3 end

Another example is as part of a mathematical function, such as log:

log(if [request.body.pi] > 3 then
    [field.value]
else
    1.23
end)

Default Values

If a key being queried in an expression is not found, a default value can be provided. To specify a default value you can use the following syntax:

[user.age|25]

In this example, if user.age is not defined, the value used will be 25.

Updated: