Log Service

Dieses Modul bietet unter anderem einen REST-Endpunkt, um Monitor-Daten entgegenzunehmen und diese nach OpenSearch und optional in eine Datenbank zu schreiben. Die betreffenden Monitor-"Clients" werden sofort per Websocket-Event über neue Daten informiert.

Soll auch in eine Datenbank geschrieben werden, dann müssen die Datenbanktabellen bereits angelegt sein. Diese werden nicht automatisch angelegt und bei Änderungen der Datenstruktur auch nicht automatisch angepasst.

Falls in eine Datenbank geschrieben wird, dann werden über eine DB-Transaktion zuerst die Datenbankdaten geschrieben (ist normalerweise fehleranfälliger) und erst danach die Daten nach OpenSearch. Beim Schreiben in die Datenbank wird zuerst ein DB-Update (siehe keys-Einstellung) durchgeführt und wenn dies fehlschlägt ein DB-Insert.

Wie bereits oben erwähnt, werden die Daten des Log-Service üblicherweise über den Monitor dargestellt. Dieser verwendet einen Process-Index (= parent Daten) sowie einen History-Index (= child Daten).
Wenn nun aber keine History/Child-Daten übertragen werden sollen, dann geben Sie diese im übertragenden JSON einfach nicht mit an ('child'-Elemente) und lassen in den Konfigurationen (siehe unten) alle 'child'-Elemente weg.

Konfigurationsparameter Log Service Modul

Pflichtparameter sind fett hervorgehoben.

Name (Key) Gruppe Beschreibung

Security_ClientCertificateAuthMandatory
(clientCertificateAuthMandatory)

security

Legt fest, ob die Log-Service-Endpunkte nur per Client Certificate Authentication aufgerufen werden können.
Default: false

OpenSearch_DataCountLimit
(es_data_count_limit)

opensearch

Maximale Anzahl Dokumente die abgefragt werden können.
Default: 100000

OpenSearch_DataViewLimit
(es_data_view_limit)

opensearch

Bis zu diesem Wert soll OpenSearch genaue Angaben über die Anzahl der Dokumente (trackTotalHitsUpTo) zurückliefern.
Default: 100000

Konfigurationsparameter je Log Service Komponente

Fett hervorgehobene Werte sind Pflichtparameter.

Name (Key) Gruppe Beschreibung

MaintenanceEnabled
(maintenanceEnabled)

config

Wenn etwas an der Datenbank oder dem OpenSearch-Index angepasst werden muss, dann kann der Endpunkt für diese Instanz in einen Wartungsmodus gesetzt werden. In diesem Fall bekommt der Aufrufer des Endpunktes einen HTTP Status 503 (Service Unavailable) zurückgeliefert.
Default: false

Keys
(keys)

config

Hier werden die Schlüsselfelder festgelegt. Beim Schreiben in die Datenbank werden diese für das DB-Update verwendet. Bei OpenSearch zur Festlegung der Dokumenten-IDs. Sind mehrere Felder betroffen, dann müssen diese durch Kommata voneinander getrennt werden.

Default:

{
  "parent": {
    "idColumns": "PROCESSID"
  },
  "child": {
    "idColumns": "PROCESSID,CHILDID"
  }
}

Fields
(fields)

config

Hier können unter anderem Feinjustierungen an den Datentypen vorgenommen werden (Mapping für die OpenSearch-Indexe).

Default
{
  "parent": {
    "fieldname": {
      "type": "binary/text/timestamp/...",
      "skipOS": false,
      "skipDB": false
    }
  },
  "child": {
    "fieldname": {
      "type": "binary/text/timestamp/...",
      "skipOS": false,
      "skipDB": false
    }
  }
}
Beispiel
{
  "parent": {},
  "child": {
    "datei": {
      "type": "binary",
      "skipOS": true,
      "skipDB": false
    },
    "kommentar": {
      "type": "text"
    }
  }
}

type

  • binary = werden in OpenSearch als 'binary' angelegt

  • text = werden in OpenSearch wie bei der Replikation mit 'raw' und 'lowercase' angelegt

  • timestamp = werden in OpenSearch als ‘date’ angelegt. Wird in der zu loggenden Nachricht anstatt eines UTC-Datums der Platzhalter 'SYSDATE' verwendet, dann setzt der Log-Service stattdessen den aktuellen Zeitstempel.

  • date:iso8601_millis = werden in OpenSearch als ‘date’ mit dem Format ‘strict_date_optional_time||epoch_millis’ angelegt

  • integer = werden in OpenSearch als ‘integer’ angelegt

  • long = werden in OpenSearch als ‘long’ angelegt

  • float = werden in OpenSearch als ‘float’ angelegt

  • double = werden in OpenSearch als ‘double’ angelegt

  • boolean = werden in OpenSearch als ‘boolean’ angelegt

In der Voreinstellung werden alle Felder nach OpenSearch und in die Datenbank geschrieben. Sollen einzelne Felder nicht geschrieben werden, dann können diese hier festgelegt werden.

skipOS

true = das Feld wird nicht nach OpenSearch geschrieben

false = das Feld wird nach OpenSearch geschrieben (Voreinstellung)

skipDB

true = das Feld wird nicht in die Datenbank geschrieben

false = das Feld wird in die Datenbank geschrieben (Voreinstellung)

OS_LoggingEnabled
(esLoggingEnabled)

opensearch

Nur wenn aktiviert, werden die Daten nach OpenSearch geschrieben.
Default: true

OS_Index
(os)

opensearch

Festlegung, in welche Indexe die Parent/Child-Daten geschrieben werden sollen. Die Indexe werden automatisch angelegt. Dabei werden die festgelegten ‘Fields’-Typen berücksichtigt.

Der Name des Index muss komplett kleingeschrieben werden.

parent.index = Name des Zielindex mit den Parent/Haupt/Eltern-Daten

child.index = Name des Zielindex mit den Child/Detail/Kind-Daten

Optional:

backup.intervalInSeconds = Intervall in dem Backups/Snapshots angelegt werden (Angabe in Sekunden)

backup.keepBackupsDurationInSeconds = Backups älter als diese Angabe werden automatisch gelöscht (Angabe in Sekunden)

Beispiel:

{
  "parent": {
    "index": "of_test_main"
  },
  "child": {
    "index": "of_test_child"
  },
  "backup": {
    "intervalInSeconds": 86400,
    "keepBackupsDurationInSeconds": 2592000
  }
}

OS_Joins
(joins)

opensearch

Können verwendet werden, um die loggenden Dokumente automatisch mit zusätzlichen Daten anzureichern. Wenn z.B. in den zu loggenden Daten nur eine Partner-ID steht und noch der Name des Partners etc. im Monitor benötigt wird. Ist die gleiche Konfiguration wie bei den 'Lookup Joins' des Replikationsdienstes.

Beispiel:

[
  {
    "keyField": "partner",
    "lookupIndex": "lookup-partner",
    "lookupKeyField": "ID.raw",
    "resultFieldsPrefix": "partner_",
    "resultFieldsExcluded": [
      "ID",
      "LASTUPDATE"
    ]
  }
]

DB_RDMSLoggingEnabled
(rdmsLoggingEnabled)

rdms

Nur wenn aktiviert, werden die Daten in die Datenbank geschrieben.
Default: false

DB_RDMS
(rdms)

rdms

Festlegung, in welche Datenbanktabellen die Parent/Child-Daten geschrieben werden sollen. Die Datenbanktabellen müssen existieren und werden nicht automatisch angelegt.

rdmsDataSourceName = Name der Data Source (Datenbankverbindung) welche unter 'Core Services.data_rdms' bereits angelegt wurde

parent.table = Name der Zieltabelle mit den Parent/Haupt/Eltern-Daten

child.table = Name der Zieltabelle mit den Child/Detail/Kind-Daten

rdmsTimeZone (optional) = Ziel-Zeitzone für Datum-Felder (verwendet intern ZoneId)

Beispiel:

{
  "rdmsDataSourceName": "oracle-xe-vpma",
  "rdmsTimeZone": "America/Los_Angeles",
  "parent": {
    "table": "OF_TEST_MAIN"
  },
  "child": {
    "table": "OF_TEST_CHILD"
  }
}

Endpunkte

Zur Verwendung der Endpunkte ist ein angemeldeter Benutzer bzw. API Key mit den jeweiligen Rollen/Rechten notwendig.

Als zusätzlichen Schutz kann über die "Log Service"-Moduleinstellung Security_ClientCertificateAuthMandatory für alle Log Service Endpunkte die Client Certificate Authentication aktiviert werden.

Method Endpoint

GET

/cxf/bpc-logservice/log/instances

Description

Get a compact overview of the available log service instances.

Only the following data is returned for each instance:

  • module instance ID

  • name

  • description

  • flag whether the instance is in maintenance mode

Returns

The log service instances as JSON.

HTTP Status Code

  • 401 : authentication could not be performed

  • 503 : BPC is currently in maintenance mode

Content-Type

  • application/json

Required Access Rights

The logged in user or API Key must have either the following role or right.

  • Role : LOG_SERVICE_USER

  • Right : LOG_SERVICE_CONFIG_GET_INSTANCES

GET

/cxf/bpc-logservice/log/instances/{instanceIdOrName}

Description

Get the current configuration of the log service instance.

Path Parameter

instanceIdOrName

this can be either the ID or a unique name of the module instance

Returns

The requested config as JSON.

HTTP Status Code

  • 401 : authentication could not be performed

  • 404 : modul instance was not found

  • 503 : BPC is currently in maintenance mode

Content-Type

  • application/json

Required Access Rights

The logged in user or API Key must have either the following role or right.

  • Role : LOG_SERVICE_USER

  • Right : LOG_SERVICE_CONFIG_GET_INSTANCE

GET

/cxf/bpc-logservice/log/{instanceIdOrName}

Description

Gets the data of log service instance.

Path Parameter

instanceIdOrName

this can be either the ID or a unique name of the module instance

Query Parameter

timezoneOffset

optional

timezoneName

optional

start

first record to be read (optional, default = 0)

limit

number of records to read (optional, default = 100, max. 10.000)

parentQuery

simple search (optional). Example city:berlin. Additional information of the Lucene Query String Syntax.

parentFilter

complex filter format like done from the monitor endpoint (optional). Example: [{"property":"processid","operator":"gte","value":1000,"source":"raw","invert":false}]

parentSort

determination by which field the parent entries should be sorted (optional, default = parent key descending). Format: fieldname|[ASC|DESC]. Example: processid|DESC. Multiple sorting instructions can be specified comma separated.

childSort

determination by which field the child entries should be sorted (optional, default = child key ascending). Format: fieldname|[ASC|DESC]. Example: childid|ASC. Multiple sorting instructions can be specified comma separated.

addChilds

should the response contain the log service child entries?. Default is true.

Returns

The requested data as JSON.

HTTP Status Code

  • 401 : authentication could not be performed

  • 404 : modul instance was not found

  • 503 : BPC or instance are currently in maintenance mode

Content-Type

  • application/json

Required Access Rights

The logged in user or API Key must have either the following role or right.

  • Role : LOG_SERVICE_USER

  • Right : LOG_SERVICE_READ_DATA

GET

/cxf/bpc-logservice/log/{instanceIdOrName}/{parentId}

Description

Get the data of a log service entry with its child entries.

Path Parameter

instanceIdOrName

this can be either the ID or a unique name of the module instance

parentId

the ID of the requested log service entry

Returns

The requested data as JSON.

HTTP Status Code

  • 401 : authentication could not be performed

  • 404 : modul instance was not found

  • 503 : BPC or instance are currently in maintenance mode

Content-Type

  • application/json

Required Access Rights

The logged in user or API Key must have either the following role or right.

  • Role : LOG_SERVICE_USER

  • Right : LOG_SERVICE_READ_DATA

GET

/cxf/bpc-logservice/log/{instanceIdOrName}/{parentId}/{childId}

Description

Get the data of a log service child entry.

Path Parameter

instanceIdOrName

this can be either the ID or a unique name of the module instance

parentId

the ID of the requested log service entry

childId

the ID of the requested child entry

Returns

The requested data as JSON.

HTTP Status Code

  • 401 : authentication could not be performed

  • 404 : modul instance was not found

  • 503 : BPC or instance are currently in maintenance mode

Content-Type

  • application/json

Required Access Rights

The logged in user or API Key must have either the following role or right.

  • Role : LOG_SERVICE_USER

  • Right : LOG_SERVICE_READ_DATA

/cxf/bpc-logservice/log/{instanceIdOrName}

Description

Writes the data provided in the body to OpenSearch and/or the database.

Consumes

  • application/json

Path Parameter

instanceIdOrName

this can be either the ID or a unique name of the module instance

Query Parameter

async

'true' to perform the log asynchronously. 'false' to perform synchronously. Default is false.

Returns

HTTP Status Code

  • 200 : data has been written

  • 400 : none or invalid data to log given

  • 401 : authentication could not be performed

  • 404 : modul instance was not found

  • 500 : failed to write the data

  • 503 : maintenance mode is active or OpenSearch and database are not activated

Content-Type

  • application/json

Required Access Rights

The logged in user or API Key must have either the following role or right.

  • Role : LOG_SERVICE_USER

  • Right : LOG_SERVICE_WRITE_DATA

/cxf/bpc-logservice/log/{instanceIdOrName}/{parentId}

Description

Deletes log service entries and their child entries from OpenSearch and the database.

Path Parameter

instanceIdOrName

this can be either the ID or a unique name of the module instance

parentId

the ID of the log service entry to delete, multiple entries can be provided and must be separated by comma

Returns

HTTP Status Code

  • 200 : data has been deleted

  • 401 : authentication could not be performed

  • 404 : modul instance was not found

  • 500 : delete failed

  • 503 : maintenance mode is active or OpenSearch and database are not activated

Content-Type

  • application/json

Required Access Rights

The logged in user or API Key must have either the following role or right.

  • Role : LOG_SERVICE_USER

  • Right : LOG_SERVICE_DELETE_DATA

/cxf/bpc-logservice/log/{instanceIdOrName}/{parentId}/{childId}

Description

Deletes log service child entries from OpenSearch and the database.

Path Parameter

instanceIdOrName

this can be either the ID or a unique name of the module instance

parentId

the ID of the log service entry to delete the child entries from

childId

the ID of the child entry to delete, multiple entries can be provided and must be separated by comma

Returns

HTTP Status Code

  • 200 : data has been deleted

  • 401 : authentication could not be performed

  • 404 : modul instance was not found

  • 500 : delete failed

  • 503 : maintenance mode is active or OpenSearch and database are not activated

Content-Type

  • application/json

Required Access Rights

The logged in user or API Key must have either the following role or right.

  • Role : LOG_SERVICE_USER

  • Right : LOG_SERVICE_DELETE_DATA

/cxf/bpc-logservice/log/{instanceIdOrName}

Description

Deletes log service entries and their child entries from OpenSearch by query. Deletion from database is not supported.

Consumes

  • application/json

Path Parameter

instanceIdOrName

this can be either the ID or a unique name of the module instance

Query Parameter

timezoneOffset

optional

timezoneName

optional

parentQuery

simple search (optional). Example city:berlin. Additional information of the Lucene Query String Syntax.

parentFilter

complex filter format like done from the monitor endpoint (optional). Example: [{"property":"processid","operator":"gte","value":1000,"source":"raw","invert":false}]

Returns

HTTP Status Code

  • 200 : data has been deleted

  • 401 : authentication could not be performed

  • 404 : modul instance was not found

  • 500 : delete failed

  • 503 : maintenance mode is active or OpenSearch and database are not activated

Content-Type

  • application/json

Required Access Rights

The logged in user or API Key must have either the following role or right.

  • Role : LOG_SERVICE_USER

  • Right : LOG_SERVICE_DELETE_DATA

/cxf/bpc-logservice/log/drop/indices/{instanceIdOrName}

Description

Deletes/drops the parent and child indices of a log service instance.

Path Parameter

instanceIdOrName

this can be either the ID or a unique name of the module instance

Returns

HTTP Status Code

  • 200 : indices deleted

  • 401 : authentication could not be performed

  • 404 : modul instance was not found

  • 500 : delete failed

  • 503 : maintenance mode is active or OpenSearch and database are not activated

Content-Type

  • application/json

Required Access Rights

The logged in user or API Key must have either the following role or right.

  • Role : LOG_SERVICE_USER

  • Right : LOG_SERVICE_DROP_INDICES

Response im Fehlerfall

Bei jedem Fehler (Status != 200) wird eine JSON mit folgendem exemplarischen Aufbau zurückgeliefert:

{
  "error": {
    "code": 301,
    "name": "MODULE_INSTANCE_NOT_FOUND",
    "message": "Did not found the instance 'of_test2' of the module 'logservice'.",
    "properties": {
      "instanceId": "of_test2",
      "moduleId": "logservice"
    }
  }
}

Die möglichen Error Codes

Error Code

Error Name

Info

1

UNEXPECTED

sollte nicht aufreten

20

MODULE_NOT_FOUND

das 'Log Service' Modul wurde nicht gefunden

21

MODULE_INSTANCE_NOT_FOUND

die 'Log Service' Instanz wurde nicht gefunden

10000

VALIDATION_MISSING_INPUT

Es wurden keine Daten zum "loggen" übergeben

10010

VALIDATION_INVALID_INPUT

Die übergebenen Daten sind kein gültiges JSON bzw. entsprechen nicht dem hier genannten Aufbau

20000

LOG_SERVICE_UNAUTHORIZED

Die Zugangsdaten stimmen nicht mit den hinterlegten überein (Log Service Modul Einstellung)

20001

LOG_SERVICE_MAINTENANCE

Der Log Service befindet sich derzeit im Wartungsmodus bzw. das DB- und OpenSearch-Logging sind deaktiviert

20002

LOG_SERVICE_MISSING_SERVICE

Ein vom Log Service benötigter Dienst steht nicht zur Verfügung

20100

LOG_SERVICE_DB_LOGGING_FAILED

Beim DB Logging ist ein Fehler aufgetreten

20101

LOG_SERVICE_DB_DELETE_FAILED

Beim Löschen von DB Einträgen ist ein Fehler aufgetreten

20102

LOG_SERVICE_DB_WRONG_TABLE_NAME

Es wurde ein ungültiger Tabellenname angegeben

20103

LOG_SERVICE_DB_TABLE_MISSING

Die verwendete Datenbank-Tabelle konnte nicht gefunden werden

20104

LOG_SERVICE_DB_COLUMN_MISSING

Die zu verwendende Datenbank-Spalte existiert nicht

20105

LOG_SERVICE_DB_GET_METADATA_FAILED

Die Metadaten der Datenbank konnten nicht ausgelesen werden

20106

LOG_SERVICE_DB_NOT_SUPPORTED

Die angeforderte Funktionalität steht bei relationalen Datenbanken nicht zur Verfügung

20201

LOG_SERVICE_OS_FAILURE

Beim Zugriff auf OpenSearch ist ein Fehler aufgetreten

20202

LOG_SERVICE_OS_FIELD_MISSING

Zum Bilden des Dokument-Schlüssels fehlen die erforderlichen Daten im zu loggenden Dokument

20203

LOG_SERVICE_OS_LOGGING_FAILED

Beim Schreiben der Daten nach OpenSearch kam es zu einem Problem

20204

LOG_SERVICE_OS_DELETE_FAILED

Beim Löschen von Dokumenten aus dem OpenSearch Index ist ein Fehler aufgetreten

20205

LOG_SERVICE_OS_QUERY_RANGE_LIMIT

Es wurden mehr Daten angefordert als zur Verfügung stehen

POST : Aufbau der zur übergebenden JSON Nachricht

Eintrag erstellen/aktualisieren

{
    "entries": [
        {
            "parent": {
            	"feldname1": wert1,
            	"feldname2": wert2,
                ...
            },
            "childs": [
                {
                    "feldname1": wert1,
                    "feldname2": wert2,
                    "datei_binary": base64_3,
                    ...
                },
                {
                    "feldname1": wert3,
                    "feldname2": wert4,
                    "datei_binary": base64_5,
                    ...
                },
                ...
            ]
        },
        ...
    ]
}

Einzelne Felder aktualisieren (Partial Update)

{
    "entries": [
        {
            "partialUpdate": true,
            "parent": {
                "feldname1": wert1,
                "feldname2": wert2
            },
            ...
        },
        ...
    ]
}

Infos zum Partial Update

Das Partial Update bezieht sich auf den Parent Eintrag und die Child Einträge. Das bedeutet, wenn man ein Parent Eintrag updaten möchte, sollte man keinen "neuen" Child Log im gleichen JSON Dokument erstellen. Wenn man beim Update eines Parent Eintrages einen neuen Child Log erstellen möchte, sollte es in zwei Log-Service-Calls implementiert werden. Es könnte beispielsweise wie folgt aussehen (siehe INFOBLOCK 1 und 2):

INFOBLOCK 1 - Beispiel Parent
{
  "entries" : [ {
    "partialUpdate" : true,
    "parent" : {
      "ANFRAGESTATUS" : "Offen",
      "ANFRAGENUMMER" : "16",
      "ANFRAGEDATUM" : "2018-01-23T14:35:41.884+0200"
    }
  } ]
}
INFOBLOCK 2 - Beispiel Child
{
  "entries" : [ {
    "childs" : [ {
      "ANFRAGENUMMER" : "16",
      "PROZESSBESCHREIBUNG" : "Status wurde geändert auf Offen",
      "STATUS" : "OK",
      "TIMESTAMP" : "2018-01-23T14:35:41.927+0200",
      "CHILDID" : 309
    } ]
  } ]
}

Im Workflow sieht das dann so aus:

log service workflow

Werte der JSON Felder

Typ Wertebereich

Boolean

true / false ohne Hochkommata

Datum

als ISO-8601 mit Hochkommata, z.B. "2017-05-17T15:28:23.181Z". In der Datenbank das Feld als java.sql.Types.TIMESTAMP

Zahlen

Wert ohne Hochkommata

Binär

Base64 encoded. In der Datenbank als java.sql.Types.BLOB anlegen.

Text

Wert mit Hochkommata

Feldnamen Konvention

Über die Modulinstanz Einstellung fields (siehe unten) können die Datentypen pro Feld festgelegt werden. Dies ist für binary-Typen nicht unbedingt notwendig und kann auch über die Feldnamen Konvention FIELDNAME_binary (_binary als Postfix) durchgeführt werden.

Hintergrund: In OpenSearch speichern wir Binärdaten Base64 encoded ab (so müssen diese auch im übergebenen JSON stehen). Diese wollen wir aber, wie bei der Replikation unter dem speziellen Typ 'attachment' ablegen. Da beides für OpenSearch Strings sind, müssen wir dies wissen und ein spezielles Mapping durchführen.

Beispiel: Wenn wir ein Feld mit dem Namen 'datei' haben und die Namen frei vergeben können, dann kann daraus der Name 'datei_binary' werden und OpenSearch legt die Daten als 'attachment' ab bzw. erstellt das Mapping dafür.

Beispiel Aufruf

curl -H "X-ApiKey: 1f697af5-c147-3d94-c529-e06f3f15bb87" \
     -H "Content-Type: application/json" \
     -XPOST 'localhost:8181/cxf/bpc-logservice/log/of_test' \
     -d @logservice.json

Erläuterung:

Option

Beschreibung

-H "X-ApiKey: 1f697af5-c147-3d94-c529-e06f3f15bb87"

Der API-Key mit der passenden Rolle bzw. passendem Recht

-H "Content-Type: application/json"

Der Content-Type muss auf application/json gesetzt sein

-XPOST

HTTP POST ist erforderlich

localhost:8181/cxf/bpc-logservice/log/of_test

of_test ist ein eindeutiger Instanz-Name. Stattdessen kann auch die ID der Modul Instanz verwendet werden

-d @logservice.json

das zu postende JSON (siehe unten)

logservice.json (Eintrag erstellen/aktualisieren)

{
    "entries": [
        {
            "parent": {
                "processid": 40,
                "name": "hello world",
                "city": "Berlin",
                "lastupdate": "2017-05-17T15:28:23.181Z"
            },
            "childs": [
                {
                    "processid": 40,
                    "childid": 1,
                    "ersteller": "Oliver",
                    "kommentar": "Bild und langer Text",
                    "datei": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAQMAAABIeJ9nAAAABlBMVEUAAAD///+l2Z/dAAAADElEQVQIHWNwYGgAAAFEAMGoX3f9AAAAAElFTkSuQmCC",
                    "langertext": "Dieser Text ist aber ziemlich .................... lang. :-)",
                    "lastupdate": "2017-05-17T15:28:23.181Z"
                }
            ]
        }
    ]
}

logservice.json (Einzelne Felder aktualisieren (Partial Update))

{
    "entries": [
        {
            "parent": {
                "processid": 40,
                "name": "hello world (aktualisiert)",
                "lastupdate": "2018-01-02T14:45:18.421Z"
            }
        }
    ]
}

Keywords: