Perform the following steps to create a custom filter that requires additional filter properties to be set in the logging.config file:
  1. Create a custom filter class.
  2. Create a custom filter builder.
  3. Register the filter class and filter builder.
The following example creates a custom filter that translates a log message into another language via a REST API. For this example, assume that the filter requires three properties to be set in the logging.config file:
  • toLang—the language that the message is to be translated in.
  • serviceURI—the URI of the REST service that performs the translation.
  • apiKey—an API key required by the translation service.

Create a custom filter class

The first step is to create the custom filter class and have it implement the ILoggerFilter interface. The ABL code that executes the filter's operations must be put inside the interface's ExecuteFilter() method. This step needs to be performed for all custom filters, regardless of whether the filter requires additional properties.

In the following example, the filter class (TranslatedMessageFormat) contains three properties—the translation service URI, the api key, and the language to translate the message to. These properties are used in the ExecuteFilter() method, along with the log message, to build an HTTP request that is sent to the translation service. The actual values for these properties are to be obtained from configurations in the logging.config file.

class Example.Filters.TranslatedMessageFormat implements OpenEdge.Logging.Filter.ILoggerFilter:
    
    // The translation service URI
    define public property TranslationService as OpenEdge.Net.URI no-undo
        get.
        set.
    
    // The API key for making requests
    define public property ApiKey as character no-undo
        get.
        set.
    
    // The language to translate the message to
    define public property To as character no-undo
        get.
        set.
    
    // reusable HTTP client
    define variable httpClient as OpenEdge.Net.HTTP.IHttpClient no-undo.
    
    constructor public TranslatedMessageFormat ( ):
        assign httpClient = OpenEdge.Net.HTTP.ClientBuilder:Build():Client.
    end constructor.
    
    method public void ExecuteFilter ( input poEvent as OpenEdge.Logging.LogEvent):
        define variable req as OpenEdge.Net.HTTP.IHttpRequest no-undo.
        define variable resp as OpenEdge.Net.HTTP.IHttpResponse no-undo.
        define variable msg as Progress.Json.ObjectModel.JsonArray no-undo.
        define variable txt as Progress.Json.ObjectModel.JsonObject no-undo.
        define variable transArray as Progress.Json.ObjectModel.JsonArray no-undo.
        define variable loop as integer no-undo.
        define variable cnt as integer no-undo.
        
        TranslationService:AddQuery('to':u, this-object:To).
        
        assign 
            req = OpenEdge.Net.HTTP.RequestBuilder:Post(TranslationService)
                  :AddHeader('Ocp-Apim-Subscription-Key':u, ApiKey)
                  :AcceptJson()
                  :ContentType('application/json':u)
                  :Request.
        
        assign 
            msg = new Progress.Json.ObjectModel.JsonArray()
            req:Entity = msg
            txt = new Progress.Json.ObjectModel.JsonObject().
        
        txt:Add('Text':u, poEvent:Message:Message).
        msg:Add(txt).
        
        assign 
            resp = httpClient:Execute(req)
            msg = cast(resp:Entity, Progress.Json.ObjectModel.JsonArray)
            transArray = msg:GetJsonObject(1):GetJsonArray('translations':u)
            cnt = transArray:Length.
        
        MSGBLK:
        do loop = 1 to cnt:
            assign txt = transArray:GetJsonObject(loop).
            if txt:GetCharacter('to':u) eq this-object:To then do:
                assign poEvent:Message:Message = txt:GetCharacter('text':u).
                leave MSGBLK.
            end.
        end.
    end method.
    
end class.

Create a custom filter builder

The properties defined in the custom filter class need to be bound to properties in the logging.config file. To do this, you need to create a custom filter builder that inherits the OpenEdge.Logging.Filter.LogFilterBuilder class and override its NewFilter() method. The method must return an ILoggerFilter object.

In this example, a JsonObject variable named options is used to map the properties defined in the custom filter class (To, ApiKey, TranslationService), with logger properties (toLang, apiKey, serviceURI) that will be used in a logging configuration in the logging.config file.

class Example.Filters.TranslatedMessageFormatBuilder inherits OpenEdge.Logging.Filter.LogFilterBuilder: 

    /* Constructor */
    constructor public TranslatedMessageFormatBuilder (input filterName as character):
        super(filterName).
    end constructor.
    
    /* Creates the instance.
       
       @return ILoggerFilter A filter instance  */
    method override protected OpenEdge.Logging.Filter.ILoggerFilter NewFilter ( ):
        define variable logFilter as OpenEdge.Logging.Filter.ILoggerFilter no-undo.
        define variable options as Progress.Json.ObjectModel.JsonObject no-undo.
        define variable tmf as Example.Filters.TranslatedMessageFormat no-undo.
        
        // Use the default
        assign logFilter = super:NewFilter().
        
        OpenEdge.Core.Assert:IsType(logFilter, get-class(Example.Filters.TranslatedMessageFormat)).
        
        assign 
            tmf = cast(logFilter, Example.Filters.TranslatedMessageFormat)
            options = cast(GetOptionObjectValue('loggerOptions':u), Progress.Json.ObjectModel.JsonObject)
            //Map filter class properties to logging configuration properties
            tmf:To = options:GetCharacter('toLang':u)
            tmf:ApiKey = options:GetCharacter('apiKey':u)
            tmf:TranslationService = OpenEdge.Net.URI:Parse(options:GetCharacter('serviceURI':u)).

        return logFilter.
    end method.
    
end class.

Register the filter class and filter builder

Finally, register the custom filter class and builder. There are two ways to do this:
  • In the logging.config file (recommended)
  • In an ABL procedure

Register the filter class and builder in the logging.config file (recommended)

To register a filter class and builder in the logging.config file, add a filter object after the logger object. Inside the filter object, specify a name for the custom filter and define two properties type and—builder. Set the filter class name as the value for type and the builder class name as the value for builder.
{
   "logger": {
      "Company.Order.Sales": {
         "logLevel": INFO,
         "filters": [
                    ...
         ]
      }      
   },
  "filter": {
        "TRANSLATION_FILTER": {
            "type":    "Example.Filters.TranslatedMessageFormat",
            "builder": "Example.Builders.TranslatedMessageFormatBuilder"
        }
    }
}

Register the filter class and builder in an ABL procedure

To register a custom filter and builder in an ABL procedure, use the OpenEdge.Logging.LoggerFilterRegistry class as shown in this example:
LoggerFilterRegistry:Registry:Put("TRANSLATION_FILTER", get-class(Example.Filters.ReverseWordsFormat)).
LogFilterBuilderRegistry:Registry:Put('TRANSLATION_FILTER':u, get-class(Example.Builders.TranslatedMessageFormatBuilder)).
You should do this early in your application code. For example, if your application is to be deployed to a PAS for OpenEdge instance, you should register the filter and its builder in a session startup procedure.

Example: Using the custom filter with additional properties

After the custom filter and the custom filter builder have been registered, you can use the filter and specify its properties in the logging.config file as shown in this example:
{
   "logger": {
      "Company.Order.Sales": {
         {
            "name": "TRANSLATION_FILTER",
            "toLang": "fr",
            "serviceURI": "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0",
            "apiKey": "<APIKey>"
         },
         {
            "name": "NAMED_FILE_WRITER",
            "fileName": "${msg.logger}-${today}.log",
            "appendTo": true
         }
      }
   },
   "filter": {
        "TRANSLATION_FILTER": {
            "type":    "Example.Filters.TranslatedMessageFormat",
            "builder": "Example.Builders.TranslatedMessageFormatBuilder"
        }
    }
}
For a log message produced from a statement like logger:Info("Customer found."), the translated log output would be "Client retrouvé.".