---
description: Pipeline for parsing Salesforce apex logs
processors:
- set:
    field: event.ingested
    value: '{{_ingest.timestamp}}'
- remove:
    field:
      - message
    ignore_missing: true

- set:
    field: salesforce.access_mode
    value: rest
    ignore_failure: true

- date:
    field: json.TIMESTAMP_DERIVED
    target_field: "@timestamp"
    formats:
    - ISO8601
    ignore_failure: true

- rename:
    field: json.CALLOUT_TIME
    target_field: salesforce.apex.callout_time
    ignore_missing: true
- rename:
    field: json.CPU_TIME
    target_field: salesforce.apex.cpu_time
    ignore_missing: true
- rename:
    field: json.DB_BLOCKS
    target_field: salesforce.apex.db_blocks
    ignore_missing: true
- rename:
    field: json.DB_CPU_TIME
    target_field: salesforce.apex.db_cpu_time
    ignore_missing: true
- rename:
    field: json.DB_TOTAL_TIME
    target_field: salesforce.apex.db_total_time
    ignore_missing: true
- rename:
    field: json.ENTITY_NAME
    target_field: salesforce.apex.entity_name
    ignore_missing: true
- rename:
    field: json.ENTRY_POINT
    target_field: salesforce.apex.entry_point
    ignore_missing: true
- rename:
    field: json.EVENT_TYPE
    target_field: salesforce.apex.event_type
    ignore_missing: true
- rename:
    field: json.IS_LONG_RUNNING_REQUEST
    target_field: salesforce.apex.is_long_running_request
    ignore_missing: true
- rename:
    field: json.LOGIN_KEY
    target_field: salesforce.apex.login_key
    ignore_missing: true
- rename:
    field: json.MEDIA_TYPE
    target_field: salesforce.apex.media_type
    ignore_missing: true
- rename:
    field: json.METHOD_NAME
    target_field: salesforce.apex.method_name
    ignore_missing: true
- rename:
    field: json.NUMBER_FIELDS
    target_field: salesforce.apex.number_fields
    ignore_missing: true
- rename:
    field: json.NUMBER_SOQL_QUERIES
    target_field: salesforce.apex.number_soql_queries
    ignore_missing: true
- rename:
    field: json.OFFSET
    target_field: salesforce.apex.offset
    ignore_missing: true
- rename:
    field: json.ORDERBY
    target_field: salesforce.apex.orderby
    ignore_missing: true
- rename:
    field: json.ORGANIZATION_ID
    target_field: salesforce.apex.organization_id
    ignore_missing: true
- rename:
    field: json.QUERY
    target_field: salesforce.apex.query
    ignore_missing: true
- rename:
    field: json.QUIDDITY
    target_field: salesforce.apex.quiddity
    ignore_missing: true
- rename:
    field: json.REQUEST_ID
    target_field: salesforce.apex.request.id
    ignore_missing: true
- rename:
    field: json.REQUEST_STATUS
    target_field: salesforce.apex.request.status
    ignore_missing: true
- rename:
    field: json.ROWS
    target_field: salesforce.apex.rows.total
    ignore_missing: true
- rename:
    field: json.ROWS_FETCHED
    target_field: salesforce.apex.rows.fetched
    ignore_missing: true
- rename:
    field: json.ROWS_PROCESSED
    target_field: salesforce.apex.rows.processed
    ignore_missing: true
- rename:
    field: json.RUN_TIME
    target_field: salesforce.apex.run_time
    ignore_missing: true
- rename:
    field: json.SELECT
    target_field: salesforce.apex.select
    ignore_missing: true
- rename:
    field: json.SUBQUERIES
    target_field: salesforce.apex.subqueries
    ignore_missing: true
- rename:
    field: json.THROUGHPUT
    target_field: salesforce.apex.throughput
    ignore_missing: true
- rename:
    field: json.TRIGGER_ID
    target_field: salesforce.apex.trigger.id
    ignore_missing: true
- rename:
    field: json.TRIGGER_NAME
    target_field: salesforce.apex.trigger.name
    ignore_missing: true
- rename:
    field: json.TRIGGER_TYPE
    target_field: salesforce.apex.trigger.type
    ignore_missing: true
- rename:
    field: json.TYPE
    target_field: salesforce.apex.type
    ignore_missing: true
- rename:
    field: json.URI
    target_field: salesforce.apex.uri
    ignore_missing: true
- rename:
    field: json.URI_ID_DERIVED
    target_field: salesforce.apex.uri_id_derived
    ignore_missing: true
- rename:
    field: json.USER_AGENT
    target_field: salesforce.apex.user_agent
    ignore_missing: true
- rename:
    field: json.USER_ID_DERIVED
    target_field: salesforce.apex.user_id_derived
    ignore_missing: true

#######################
## ECS Event Mapping ##
#######################

- set:
    field: event.dataset
    value: "salesforce.apex"
- set:
    field: event.kind
    value: "event"
- set:
    field: event.module
    value: "salesforce"

- set:
    field: event.type
    value: "connection"
    if: 'ctx?.salesforce?.apex?.event_type != "ApexExecution"'

- set:
    field: event.category
    value: "network"
    if: 'ctx?.salesforce?.apex?.event_type != "ApexTrigger" && ctx?.salesforce?.apex?.event_type != "ApexExecution"'

- script:
      if: ctx?.salesforce?.apex?.event_type != null && ctx?.salesforce?.apex?.event_type != "" 
      lang: painless
      source: |
        def eventType = ctx?.salesforce?.apex?.event_type?.toLowerCase();
        Map referenceTable = [
          "apexcallout": "apex-callout",
          "apextrigger": "apex-trigger",
          "apexexecution": "apex-execution",
          "apexrestApi": "apex-rest",
          "apexsoap": "apex-soap",
          "externalcustomapexcallout": ["apex-external-custom-callout"]
        ];

        ctx.event.action = referenceTable[eventType];

- rename:
    field: json.TIME
    target_field: event.duration
    if: 'ctx?.salesforce?.apex?.event_type == "ApexCallout" && ctx?.json?.TIME != ""'
- rename:
    field: json.EXEC_TIME
    target_field: event.duration
    if: '(ctx?.salesforce?.apex?.event_type == "ApexTrigger" || ctx?.salesforce?.apex?.event_type == "ApexExecution") && ctx?.json?.EXEC_TIME != ""'
- rename:
    field: salesforce.apex.run_time
    target_field: event.duration
    if: '(ctx?.salesforce?.apex?.event_type == "ApexRestApi" || ctx?.salesforce?.apex?.event_type == "ApexSoap") && ctx?.salesforce?.apex?.run_time != ""'
- rename:
    field: json.TOTAL_MS
    target_field: event.duration
    if: 'ctx?.salesforce?.apex?.event_type == "ExternalCustomApexCallout" && ctx?.json?.TOTAL_MS != ""'

- set:
    field: event.outcome
    value: success
    if: 'ctx?.json?.SUCCESS == "1" && ctx?.json?.SUCCESS != null'
    ignore_failure: true
- set:
    field: event.outcome
    value: failure
    if: 'ctx?.json?.SUCCESS != "1" && ctx?.json?.SUCCESS != null'
    ignore_failure: true
- set:
    field: event.outcome
    value: success
    if: 'ctx?.json?.STATUS == "1" && ctx?.json?.STATUS != null'
    ignore_failure: true
- set:
    field: event.outcome
    value: failure
    if: 'ctx?.json?.STATUS != "1" && ctx?.json?.STATUS != null'
    ignore_failure: true

- rename:
    field: json.URL
    target_field: event.url
    ignore_missing: true
    if: 'ctx?.salesforce?.apex?.event_type == "ApexCallout"'
- rename:
    field: salesforce.apex.uri
    target_field: event.url
    ignore_missing: true
    if: 'ctx?.salesforce?.apex?.event_type != "ApexCallout" && ctx?.salesforce?.apex?.event_type != "ExternalCustomApexCallout"'

######################
## ECS User Mapping ##
######################

- rename:
    field: json.USER_ID
    target_field: user.id
    ignore_missing: true
- rename:
    field: json.USER_TYPE
    target_field: user.roles
    ignore_missing: true

########################
## ECS Source Mapping ##
########################

# A Salesforce internal IP (such as a login from Salesforce Workbench or AppExchange) is shown as “Salesforce.com IP”
- rename:
    field: json.CLIENT_IP
    target_field: source.ip
    ignore_missing: true
    if: 'ctx?.json?.CLIENT_IP != "Salesforce.com IP" && ctx?.json?.CLIENT_IP != "" && ctx?.json?.CLIENT_IP != null'

############################
## ECS Source.Geo Mapping ##
############################

# A Salesforce internal IP (such as a login from Salesforce Workbench or AppExchange) is shown as “Salesforce.com IP”
- geoip:
    field: source.ip
    target_field: source.geo
    ignore_missing: true
    if: 'ctx?.source?.ip != "Salesforce.com IP" && ctx?.source?.ip != "" && ctx?.source?.ip != null'

############################
## ECS Related.ip Mapping ##
############################

- append:
    field: related.ip
    value: "{{{source.ip}}}"
    if: ctx?.source?.ip != null
    allow_duplicates: false
    ignore_failure: true

######################
## ECS HTTP Mapping ##
######################

- rename:
    field: json.METHOD
    target_field: http.request.method
    ignore_missing: true
- rename:
    field: json.REQUEST_SIZE
    target_field: http.request.bytes
    ignore_missing: true
- rename:
    field: json.RESPONSE_SIZE
    target_field: http.response.bytes
    ignore_missing: true
- rename:
    field: json.STATUS_CODE
    target_field: http.response.status_code
    ignore_missing: true

#############
## Cleanup ##
#############

- script:
    description: Drops null/empty values recursively
    lang: painless
    source: |
        boolean dropEmptyFields(Object object) {
            if (object == null || object == "") {
                return true;
            } else if (object instanceof Map) {
                ((Map) object).values().removeIf(value -> dropEmptyFields(value));
                return (((Map) object).size() == 0);
            } else if (object instanceof List) {
                ((List) object).removeIf(value -> dropEmptyFields(value));
                return (((List) object).length == 0);
            }
            return false;
        }
        dropEmptyFields(ctx);
- remove:
    field:
      - json
    ignore_missing: true
on_failure:
- set:
    field: error.message
    value: '{{_ingest.on_failure_message}}'
