Consejos y solución de problemas al escribir analizadores

Disponible en:

En este documento se describen los problemas que pueden surgir al escribir código de analizador.

Al escribir código de analizador, es posible que se produzcan errores cuando las instrucciones de análisis no funcionen como se espera. Estas son algunas de las situaciones que pueden generar errores:

  • Falla un patrón Grok
  • Falla una operación de rename o replace
  • Errores de sintaxis en el código del analizador

Prácticas habituales en el código del analizador

En las siguientes secciones se describen las prácticas recomendadas, los consejos y las soluciones para solucionar problemas.

No uses puntos ni guiones en los nombres de las variables

El uso de guiones y puntos en los nombres de las variables puede provocar un comportamiento inesperado, sobre todo al realizar operaciones merge para almacenar valores en campos de UDM. También es posible que tengas problemas de análisis intermitentes.

Por ejemplo, no utilices los siguientes nombres de variables:

  • my.variable.result
  • my-variable-result

En su lugar, usa el siguiente nombre de variable: my_variable_result.

No uses términos con un significado especial como nombre de variable

Algunas palabras, como event y timestamp, pueden tener un significado especial en el código del analizador.

La cadena event se suele usar para representar un solo registro de UDM y se utiliza en la instrucción @output. Si un mensaje de registro incluye un campo llamado event o si defines una variable intermedia llamada event y el código del analizador usa la palabra event en la instrucción @output, recibirás un mensaje de error sobre un conflicto de nombres.

Cambie el nombre de la variable intermedia por otro o use el término event1 como prefijo en los nombres de los campos de la gestión de datos unificada y en la instrucción @output.

La palabra timestamp representa la marca de tiempo de creación del registro sin procesar original. El valor definido en esta variable intermedia se guarda en el campo de UDM metadata.event_timestamp. El término @timestamp representa la fecha y la hora en las que se analizó el registro sin procesar para crear un registro UDM.

En el ejemplo siguiente se asigna al campo metadata.event_timestamp UDM la fecha y la hora en las que se analizó el registro sin procesar.

 # Save the log parse date and time to the timestamp variable
  mutate {
     rename => {
       "@timestamp" => "timestamp"
     }
   }

En el siguiente ejemplo, se asigna al campo metadata.event_timestamp de UDM la fecha y la hora extraídas del registro sin procesar original y almacenadas en la variable intermedia when.

   # Save the event timestamp to timestamp variable
   mutate {
     rename => {
       "when" => "timestamp"
     }
   }

No utilices los siguientes términos como variables:

  • collectiontimestamp
  • createtimestamp
  • evento
  • filename
  • mensaje
  • espacio de nombres
  • salida
  • onerrorcount
  • timestamp
  • timezone

Almacenar cada valor de datos en un campo de UDM independiente

No almacene varios campos en un solo campo de UDM concatenándolos con un delimitador. A continuación, se muestra un ejemplo:

"principal.user.first_name" => "first:%{first_name},last:%{last_name}"

En su lugar, almacena cada valor en un campo de UDM independiente.

"principal.user.first_name" => "%{first_name}"
"principal.user.last_name" => "%{last_name}"

Usar espacios en lugar de tabulaciones en el código

No uses tabulaciones en el código del analizador. Usa solo espacios y sangra 2 espacios cada vez.

No realices varias acciones de combinación en una sola operación

Si combinas varios campos en una sola operación, puede que los resultados no sean coherentes. En su lugar, coloca las instrucciones merge en operaciones independientes.

Por ejemplo, sustituye el siguiente ejemplo:

mutate {
  merge => {
      "security_result.category_details" => "category_details"
      "security_result.category_details" => "super_category_details"
  }
}

Con esta opción:

mutate {
  merge => {
    "security_result.category_details" => "category_details"
  }
}

mutate {
  merge => {
    "security_result.category_details" => "super_category_details"
  }
}

Elegir entre expresiones condicionales if y if else

Si el valor condicional que estás probando solo puede tener una coincidencia, usa la instrucción condicional if else. Este enfoque es ligeramente más eficiente. Sin embargo, si se da el caso de que el valor probado puede coincidir más de una vez, utilice varias instrucciones if distintas y ordene las instrucciones del caso más genérico al más específico.

Elige un conjunto representativo de archivos de registro para probar los cambios del analizador

Una práctica recomendada es probar el código del analizador con muestras de registros sin procesar que tengan una amplia variedad de formatos. De esta forma, puede encontrar registros únicos o casos límite que el analizador pueda tener que gestionar.

Añadir comentarios descriptivos al código del analizador

Añade comentarios al código del analizador que expliquen por qué es importante la instrucción, en lugar de lo que hace. El comentario ayuda a cualquier persona que mantenga el analizador a seguir el flujo. A continuación, se muestra un ejemplo:

# only assign a Namespace if the source address is RFC 1918 or Loopback IP address
if [jsonPayload][id][orig_h] =~ /^(127(?:\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\{3\}$)|(10(?:\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\{3\}$)|(192\.168(?:\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\{2\}$)|(172\.(?:1[6-9]|2\d|3[0-1])(?:\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\{2\}$)/ {
  mutate {
    replace => {
      "event1.idm.read_only_udm.principal.namespace" => "%{resource.labels.project_id}"
    }
  }
}

Inicializa las variables intermedias pronto

Antes de extraer valores del registro sin procesar original, inicializa las variables intermedias que se usarán para almacenar los valores de prueba.

De esta forma, se evita que se devuelva un error que indique que la variable intermedia no existe.

La siguiente instrucción asigna el valor de la variable product al campo de UDM metadata.product_name.

mutate{
  replace => {
    "event1.idm.read_only_udm.metadata.product_name" => "%{product}"
  }
}

Si la variable product no existe, se mostrará el siguiente error:

"generic::invalid_argument: pipeline failed: filter mutate (4) failed: replace failure: field \"event1.idm.read_only_udm.metadata.product_name\": source field \"product\": field not set"

Puedes añadir una instrucción on_error para detectar el error. A continuación, se muestra un ejemplo:

mutate{
  replace => {
    "event1.idm.read_only_udm.metadata.product_name" => "%{product}"
    }
  on_error => "_error_does_not_exist"
  }

La instrucción del ejemplo anterior detecta correctamente el error de análisis y lo asigna a una variable intermedia booleana llamada _error_does_not_exist. No te permite usar la variable product en una instrucción condicional, como if. A continuación, se muestra un ejemplo:

if [product] != "" {
  mutate{
    replace => {
      "event1.idm.read_only_udm.metadata.product_name" => "%{product}"
    }
  }
  on_error => "_error_does_not_exist"
}

En el ejemplo anterior se devuelve el siguiente error porque la cláusula condicional if no admite instrucciones on_error:

"generic::invalid_argument: pipeline failed: filter conditional (4) failed: failed to evaluate expression: generic::invalid_argument: "product" not found in state data"

Para solucionar este problema, añade un bloque de instrucciones independiente que inicialice las variables intermedias antes de ejecutar las instrucciones del filtro de extracción (json, csv, xml, kv o grok). A continuación, se muestra un ejemplo.

filter {
  # Initialize intermediate variables for any field you will use for a conditional check
  mutate {
    replace => {
      "timestamp" => ""
      "does_not_exist" => ""
    }
  }

  # load the logs fields from the message field
  json {
    source         => "message"
    array_function => "split_columns"
    on_error       => "_not_json"
  }
}

El fragmento de código del analizador actualizado gestiona los distintos casos mediante una instrucción condicional para comprobar si existe el campo. Además, la instrucción on_error gestiona los errores que se puedan producir.

Convertir SHA-256 en base64

En el siguiente ejemplo se extrae el valor SHA-256, se codifica en base64, se convierten los datos codificados en una cadena hexadecimal y, a continuación, se sustituyen campos específicos por los valores extraídos y procesados.

if [Sha256] != "" 
{
  base64
  {
  encoding => "RawStandard"
  source => "Sha256"
  target => "base64_sha256"
  on_error => "base64_message_error"
  }
  mutate
  {
    convert =>
    {
      "base64_sha256" => "bytestohex"
    }
    on_error => "already_a_string"
  }
  mutate
  {
    replace => 
  {
     "event.idm.read_only_udm.network.tls.client.certificate.sha256" => "%{base64_sha256}"
     "event.idm.read_only_udm.target.resource.name" => "%{Sha256}"
  }
  }
}

Gestionar errores en las instrucciones del analizador

Es habitual que los registros entrantes tengan un formato inesperado o que los datos tengan un formato incorrecto.

Puedes crear el analizador para que gestione estos errores. Te recomendamos que añadas controladores on_error al filtro de extracción y, a continuación, pruebes la variable intermedia antes de pasar al siguiente segmento de la lógica del analizador.

En el siguiente ejemplo se usa el filtro de extracción json con una instrucción on_error para definir la variable booleana _not_json. Si _not_json tiene el valor true, significa que la entrada de registro entrante no tenía un formato JSON válido y no se ha analizado correctamente. Si la variable _not_json es false, la entrada de registro entrante tenía un formato JSON válido.

 # load the incoming log from the default message field
  json {
    source         => "message"
    array_function => "split_columns"
    on_error       => "_not_json"
  }

También puedes comprobar si un campo tiene el formato correcto. En el siguiente ejemplo se comprueba si _not_json tiene el valor true, lo que indica que el registro no tiene el formato esperado.

 # Test that the received log matches the expected format
  if [_not_json] {
    drop { tag => "TAG_MALFORMED_MESSAGE" }
  } else {
    # timestamp is always expected
    if [timestamp] != "" {

      # ...additional parser logic goes here …

    } else {

      # if the timestamp field does not exist, it's not a log source
      drop { tag => "TAG_UNSUPPORTED" }
    }
  }

De esta forma, el análisis no fallará si los registros se ingieren con un formato incorrecto para el tipo de registro especificado.

Usa el filtro drop con la variable tag para que la condición se registre en la tabla de métricas de ingestión de BigQuery.

  • TAG_UNSUPPORTED
  • TAG_MALFORMED_ENCODING
  • TAG_MALFORMED_MESSAGE
  • TAG_NO_SECURITY_VALUE

El filtro drop impide que el analizador procese el registro sin procesar, normalice los campos y cree un registro de UDM. El registro sin procesar original se sigue ingiriendo en Google Security Operations y se puede buscar mediante la búsqueda de registros sin procesar en Google SecOps.

El valor que se indica en la variable tag se almacena en el campo drop_reason_code de la tabla de métricas de ingestión. Puedes ejecutar una consulta ad hoc en la tabla similar a la siguiente:

SELECT
  log_type,
  drop_reason_code,
  COUNT(drop_reason_code) AS count
FROM `datalake.ingestion_metrics`
GROUP BY 1,2
ORDER BY 1 ASC

Solucionar problemas de errores de validación

Al crear un analizador, es posible que se produzcan errores relacionados con la validación. Por ejemplo, puede que no se haya definido un campo obligatorio en el registro de UDM. El error puede ser similar al siguiente:

Error: generic::unknown: invalid event 0: LOG_PARSING_GENERATED_INVALID_EVENT: "generic::invalid_argument: udm validation failed: target field is not set"

El código del analizador se ejecuta correctamente, pero el registro de UDM generado no incluye todos los campos de UDM obligatorios definidos por el valor asignado a metadata.event_type. A continuación, se muestran otros ejemplos que pueden provocar este error:

  • Si metadata.event_type es USER_LOGIN y el campo de UDM target.user value no está definido.
  • Si metadata.event_type es NETWORK_CONNECTION y el campo target.hostnameUDM no está definido.

Para obtener más información sobre el campo metadata.event_type UDM y los campos obligatorios, consulta la guía de uso de UDM.

Una opción para solucionar este tipo de error es empezar asignando valores estáticos a los campos de UDM. Una vez que hayas definido todos los campos de UDM necesarios, examina el registro sin procesar original para ver qué valores quieres analizar y guardar en el registro de UDM. Si el registro sin procesar original no contiene determinados campos, es posible que tengas que definir valores predeterminados.

A continuación, se muestra una plantilla de ejemplo específica para el tipo de evento USER_LOGIN que ilustra este enfoque.

Ten en cuenta lo siguiente:

  • La plantilla inicializa variables intermedias y asigna a cada una una cadena estática.
  • El código de la sección Asignación de campos asigna los valores de las variables intermedias a los campos de UDM.

Puede ampliar este código añadiendo variables intermedias y campos de UDM adicionales. Una vez que haya identificado todos los campos de UDM que deben rellenarse, haga lo siguiente:

  • En la sección Input Configuration (Configuración de entrada), añade código que extraiga campos del registro sin procesar original y asigne los valores a las variables intermedias.

  • En la sección Extracción de fecha, añada el código que extrae la marca de tiempo del evento del registro sin procesar original, la transforma y la asigna a la variable intermedia.

  • Si es necesario, sustituya el valor inicializado definido en cada variable intermedia por una cadena vacía.

filter {
 mutate {
   replace => {
     # UDM > Metadata
     "metadata_event_timestamp"    => ""
     "metadata_vendor_name"        => "Example"
     "metadata_product_name"       => "Example SSO"
     "metadata_product_version"    => "1.0"
     "metadata_product_event_type" => "login"
     "metadata_product_log_id"     => "12345678"
     "metadata_description"        => "A user logged in."
     "metadata_event_type"         => "USER_LOGIN"

     # UDM > Principal
     "principal_ip"       => "192.168.2.10"

     # UDM > Target
     "target_application"            => "Example Connect"
     "target_user_user_display_name" => "Mary Smith"
     "target_user_userid"            => "mary@example.com"

     # UDM > Extensions
     "auth_type"          => "SSO"
     "auth_mechanism"     => "USERNAME_PASSWORD"

     # UDM > Security Results
     "securityResult_action"         => "ALLOW"
     "security_result.severity"       => "LOW"

   }
 }

 # ------------ Input Configuration  --------------
  # Extract values from the message using one of the extraction filters: json, kv, grok

 # ------------ Date Extract  --------------
 # If the  date {} function is not used, the default is the normalization process time

  # ------------ Field Assignment  --------------
  # UDM Metadata
  mutate {
    replace => {
      "event1.idm.read_only_udm.metadata.vendor_name"        =>  "%{metadata_vendor_name}"
      "event1.idm.read_only_udm.metadata.product_name"       =>  "%{metadata_product_name}"
      "event1.idm.read_only_udm.metadata.product_version"    =>  "%{metadata_product_version}"
      "event1.idm.read_only_udm.metadata.product_event_type" =>  "%{metadata_product_event_type}"
      "event1.idm.read_only_udm.metadata.product_log_id"     =>  "%{metadata_product_log_id}"
      "event1.idm.read_only_udm.metadata.description"        =>  "%{metadata_description}"
      "event1.idm.read_only_udm.metadata.event_type"         =>  "%{metadata_event_type}"
    }
  }

  # Set the UDM > auth fields
  mutate {
    replace => {
      "event1.idm.read_only_udm.extensions.auth.type"        => "%{auth_type}"
    }
    merge => {
      "event1.idm.read_only_udm.extensions.auth.mechanism"   => "auth_mechanism"
    }
  }

  # Set the UDM > principal fields
  mutate {
    merge => {
      "event1.idm.read_only_udm.principal.ip"                => "principal_ip"
    }
  }

  # Set the UDM > target fields
  mutate {
    replace => {
      "event1.idm.read_only_udm.target.user.userid"             =>  "%{target_user_userid}"
      "event1.idm.read_only_udm.target.user.user_display_name"  =>  "%{target_user_user_display_name}"
      "event1.idm.read_only_udm.target.application"             =>  "%{target_application}"
    }
  }

  # Set the UDM > security_results fields
  mutate {
    merge => {
      "security_result.action" => "securityResult_action"
    }
  }

  # Set the security result
  mutate {
    merge => {
      "event1.idm.read_only_udm.security_result" => "security_result"
    }
  }

 # ------------ Output the event  --------------
  mutate {
    merge => {
      "@output" => "event1"
    }
  }

}

Analizar texto no estructurado con una función de Grok

Cuando usas una función Grok para extraer valores de texto sin estructurar, puedes usar patrones Grok predefinidos y expresiones regulares. Grok patrones hace que el código sea más fácil de leer. Si la expresión regular no incluye caracteres abreviados (como \w o \s), puedes copiar y pegar la instrucción directamente en el código del analizador.

Como los patrones Grok son una capa de abstracción adicional en la instrucción, pueden hacer que la resolución de problemas sea más compleja cuando se produce un error. A continuación, se muestra un ejemplo de función Grok que contiene tanto patrones Grok predefinidos como expresiones regulares.

grok {
  match => {
    "message" => [
      "%{NUMBER:when}\\s+\\d+\\s%{SYSLOGHOST:srcip} %{WORD:action}\\/%{NUMBER:returnCode} %{NUMBER:size} %{WORD:method} (?P<url>\\S+) (?P<username>.*?) %{WORD}\\/(?P<tgtip>\\S+).*"
    ]
  }
}

Una instrucción de extracción sin patrones Grok puede tener un mejor rendimiento. Por ejemplo, el siguiente ejemplo requiere menos de la mitad de los pasos de procesamiento para encontrar una coincidencia. Se trata de un aspecto importante en el caso de las fuentes de registros con un volumen potencialmente alto.

Diferencias entre las expresiones regulares RE2 y PCRE

Los analizadores de Google SecOps usan RE2 como motor de expresiones regulares. Si conoces la sintaxis PCRE, puede que observes diferencias. A continuación, se muestra un ejemplo:

A continuación, se muestra una instrucción PCRE: (?<_custom_field>\w+)\s

A continuación, se muestra una instrucción RE2 para el código del analizador: (?P<_custom_field>\\w+)\\s

Asegúrate de usar caracteres de escape para los caracteres de escape

Google SecOps almacena los datos de registro sin procesar entrantes en formato codificado JSON. De esta forma, se asegura de que las cadenas de caracteres que parecen abreviaturas de expresiones regulares se interpreten como cadenas literales. Por ejemplo, \t se interpreta como la cadena literal y no como un carácter de tabulación.

El siguiente ejemplo muestra un registro sin procesar original y el registro en formato codificado JSON. Fíjate en el carácter de escape que se ha añadido delante de cada carácter de barra invertida que rodea el término entry.

A continuación, se muestra el registro sin procesar original:

field=\entry\

A continuación, se muestra el registro convertido al formato codificado en JSON:

field=\\entry\\

Cuando se usa una expresión regular en el código del analizador, debe añadir caracteres de escape adicionales si solo quiere extraer el valor. Para buscar una coincidencia con una barra inversa en el registro sin formato original, utiliza cuatro barras inversas en la instrucción de extracción.

A continuación, se muestra una expresión regular para el código del analizador:

^field=\\\\(?P<_value>.*)\\\\$

Este es el resultado generado. El grupo llamado _value almacena el término entry:

"_value": "entry"

Cuando mueva una instrucción de expresión regular estándar al código del analizador, incluya caracteres de escape en la instrucción de extracción. Por ejemplo, cambia \s por \\s.

Dejar sin cambios los caracteres especiales de expresiones regulares cuando se escapen dos veces en la instrucción de extracción. Por ejemplo, \\ no cambia y sigue siendo \\.

Esta es una expresión regular estándar:

^.*?\\\"(?P<_user>[^\\]+)\\\"\s(?:(logged\son|logged\soff))\s.*?\\\"(?P<_device>[^\\]+)\\\"\.$

La siguiente expresión regular se ha modificado para que funcione en el código del analizador.

^.*?\\\"(?P<_user>[^\\\\]+)\\\"\\s(?:(logged\\son|logged\\soff))\\s.*?\\\"(?P<_device>[^\\\\]+)\\\"\\.$

En la siguiente tabla se resume cuándo debe incluir una expresión regular estándar caracteres de escape adicionales antes de incluirla en el código del analizador.

Expresión regular Expresión regular modificada para el código del analizador Descripción del cambio
\s
\\s
Los caracteres abreviados deben incluirse entre caracteres de escape.
\.
\\.
Los caracteres reservados deben incluirse con formato de escape.
\\"
\\\"
Los caracteres reservados deben incluirse con formato de escape.
\]
\\]
Los caracteres reservados deben incluirse con formato de escape.
\|
\\|
Los caracteres reservados deben incluirse con formato de escape.
[^\\]+
[^\\\\]+
Los caracteres especiales de un grupo de clases de caracteres deben tener el formato de escape.
\\\\
\\\\
Los caracteres especiales que no forman parte de un grupo de clases de caracteres o de caracteres abreviados no requieren un carácter de escape adicional.

Las expresiones regulares deben incluir un grupo de captura con nombre

Una expresión regular, como "^.*$", es una sintaxis RE2 válida. Sin embargo, en el código del analizador falla y se muestra el siguiente error:

"ParseLogEntry failed: pipeline failed: filter grok (0) failed: failed to parse data with all match
patterns"

Debes añadir un grupo de captura válido a la expresión. Si usas patrones Grok, estos incluyen un grupo de captura con nombre de forma predeterminada. Cuando uses sustituciones de expresiones regulares, asegúrate de incluir un grupo con nombre.

A continuación, se muestra un ejemplo de expresión regular en el código del analizador:

"^(?P<_catchall>.*$)"

Este es el resultado, que muestra el texto asignado al grupo con el nombre _catchall.

"_catchall": "User \"BOB\" logged on to workstation \"DESKTOP-01\"."

Usa un grupo con nombre genérico para empezar a crear la expresión.

Cuando cree una instrucción de extracción, empiece con una expresión que incluya más de lo que quiere. A continuación, despliega la expresión campo por campo.

En el siguiente ejemplo, se empieza usando un grupo con nombre (_catchall) que coincide con todo el mensaje. A continuación, crea la expresión por pasos buscando porciones adicionales del texto. En cada paso, el _catchallgrupo con nombre contiene menos texto original. Continúa e itera paso a paso para hacer coincidir el mensaje hasta que ya no necesites el grupo con el nombre _catchall.

Paso Expresión regular en el código del analizador Resultado del grupo de captura con nombre _catchall
1
"^(?P<_catchall>.*$)"
User \"BOB\" logged on to workstation \"DESKTOP-01\".
2
^User\s\\\"(?P<_catchall>.*$)
BOB\" logged on to workstation \"DESKTOP-01\".
3
^User\s\\\"(?P<_user>.*?)\\\"\s(?P<_catchall>.*$)
logged on to workstation \"DESKTOP-01\".
Continúa hasta que la expresión coincida con toda la cadena de texto.

Evitar caracteres abreviados en la expresión regular

Recuerda que debes usar caracteres de escape abreviados de expresiones regulares cuando utilices la expresión en el código del analizador. A continuación, se muestra una cadena de texto de ejemplo y la expresión regular estándar que extrae la primera palabra, This.

  This is a sample log.

La siguiente expresión regular estándar extrae la primera palabra, This. Sin embargo, cuando ejecutas esta expresión en el código del analizador, falta la letra s.

Expresión regular estándar Resultado del grupo de captura con nombre _firstWord
"^(?P<_firstWord>[^\s]+)\s.*$" "_firstWord": "Thi",

Esto se debe a que las expresiones regulares del código del analizador requieren que se añada un carácter de escape adicional a los caracteres abreviados. En el ejemplo anterior, \s debe cambiarse por \\s.

Expresión regular revisada para el código del analizador Resultado del grupo de captura con nombre _firstWord
"^(?P<_firstWord>[^\\s]+)\\s.*$" "_firstWord": "This",

Esto solo se aplica a los caracteres abreviados, como \s, \r y \t. No es necesario usar caracteres de escape para otros caracteres, como ``,.

Ejemplo completo

En esta sección se describen las reglas anteriores como un ejemplo de principio a fin. Aquí tienes una cadena de texto no estructurada y la expresión regular estándar escrita para analizar la cadena. Por último, incluye la expresión regular modificada que funciona en el código del analizador.

A continuación, se muestra la cadena de texto original.

User "BOB" logged on to workstation "DESKTOP-01".

A continuación, se muestra una expresión regular RE2 estándar que analiza la cadena de texto.

^.*?\\\"(?P<_user>[^\\]+)\\\"\s(?:(logged\son|logged\soff))\s.*?\\\"(?P<_device>[^\\]+)\\\"\.$

Esta expresión extrae los siguientes campos.

Grupo de coincidencias Posición del carácter Cadena de texto
Coincidencia completa 0-53
User \"BOB\" logged on to workstation \"DESKTOP-01\".
Grupo `_user` 7-10
BOB
Grupo 2. 13-22
logged on
Grupo `_device` 40-50
DESKTOP-01

Esta es la expresión modificada. La expresión regular RE2 estándar se ha modificado para que funcione en el código del analizador.

^.*?\\\"(?P<_user>[^\\\\]+)\\\"\\s(?:(logged\\son|logged\\soff))\\s.*?\\\"(?P<_device>[^\\\\]+)\\\"\\.$

¿Necesitas más ayuda? Recibe respuestas de los miembros de la comunidad y de los profesionales de Google SecOps.