Sugerencias y solución de problemas al escribir analizadores

Compatible con:

En este documento, se describen los problemas que puedes encontrar cuando escribes código de analizador.

Cuando escribas código de analizador, es posible que encuentres errores cuando las instrucciones de análisis no funcionen según lo esperado. Entre las situaciones que podrían generar errores, se incluyen las siguientes:

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

Prácticas comunes en el código del analizador

En las siguientes secciones, se describen las prácticas recomendadas, las sugerencias y las soluciones para ayudarte a solucionar problemas.

Evita usar puntos o guiones en los nombres de las variables

El uso de guiones y puntos en los nombres de las variables puede provocar un comportamiento inesperado, a menudo cuando se realizan operaciones de merge para almacenar valores en los campos de UDM. También es posible que encuentres problemas de análisis intermitentes.

Por ejemplo, no uses 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.

Cambia el nombre de la variable intermedia por otro o usa el término event1 como prefijo en los nombres de los campos del UDM y en la instrucción @output.

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

En el siguiente ejemplo, se establece el campo metadata.event_timestamp del UDM en la fecha y hora en 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 establece el campo metadata.event_timestamp del UDM en la fecha y 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 uses los siguientes términos como variables:

  • collectiontimestamp
  • createtimestamp
  • evento
  • filename
  • mensaje
  • espacio de nombres
  • output
  • onerrorcount
  • timestamp
  • Zona horaria

Almacena cada valor de datos en un campo de UDM independiente.

No almacenes varios campos en un solo campo del 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}"

Usa espacios en lugar de tabulaciones en el código

No uses tabulaciones en el código del analizador. Usa solo espacios y aplica una sangría de 2 espacios a la vez.

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

Si combinas varios campos en una sola operación, es posible que obtengas resultados incoherentes. En cambio, coloca las instrucciones merge en operaciones separadas.

Por ejemplo, reemplaza el siguiente ejemplo:

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

Con este bloque:

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

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

Cómo elegir entre expresiones condicionales if y if else

Si el valor condicional que estás probando solo puede tener una sola coincidencia, usa la instrucción condicional if else. Este enfoque es un poco más eficiente. Sin embargo, si tienes una situación en la que el valor probado podría coincidir más de una vez, usa varias instrucciones if distintas y ordénalas desde el caso más genérico hasta el 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 con una amplia variedad de formatos. Esto te permite encontrar registros únicos o casos extremos que el analizador podría necesitar controlar.

Agrega comentarios descriptivos al código del analizador

Agrega comentarios al código del analizador que expliquen por qué la instrucción es importante, 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 variables intermedias con anticipación

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

Esto evita que se muestre un error que indique que la variable intermedia no existe.

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

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

Si la variable product no existe, recibirás 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 agregar 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 en una variable intermedia booleana, llamada _error_does_not_exist. No te permite usar la variable product en una instrucción condicional, por ejemplo, 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 muestra 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, agrega un bloque de instrucciones independiente que inicialice las variables intermedias antes de ejecutar el filtro de extracción (instrucciones 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 actualizado del analizador controla las múltiples situaciones con una instrucción condicional para verificar si existe el campo. Además, la instrucción on_error controla los errores que se pueden encontrar.

Convierte SHA-256 a Base64

En el siguiente ejemplo, se extrae el valor SHA-256, se codifica en base64, se convierte los datos codificados en una cadena hexadecimal y, luego, se reemplazan campos específicos con 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}"
  }
  }
}

Cómo controlar errores en las instrucciones del analizador

Es común que los registros entrantes tengan un formato inesperado o datos con un formato incorrecto.

Puedes compilar el analizador para que controle estos errores. Una práctica recomendada es agregar controladores on_error al filtro de extracción y, luego, probar la variable intermedia antes de continuar con el 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 establecer la variable booleana _not_json. Si _not_json se establece en true, significa que la entrada de registro entrante no tenía un formato JSON válido y que no se analizó 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 probar si un campo tiene el formato correcto. En el siguiente ejemplo, se verifica si _not_json está configurado como true, lo que indica que el registro no tenía 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" }
    }
  }

Esto garantiza que el análisis no falle si los registros se transfieren con un formato incorrecto para el tipo de registro especificado.

Usa el filtro drop con la variable tag para que la condición se capture en la tabla de métricas de transferencia en 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 aún se transfiere a Google Security Operations y se puede buscar con la búsqueda de registros sin procesar en Google SecOps.

El valor que se pasa a la variable tag se almacena en el campo drop_reason_code de la tabla Ingestion metrics. Puedes ejecutar una consulta ad hoc en la tabla de forma 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

Soluciona problemas de errores de validación

Cuando compilas un analizador, es posible que encuentres errores relacionados con la validación, por ejemplo, que no se haya establecido 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 obligatorios de UDM según lo define el conjunto de valores establecido en metadata.event_type. A continuación, se incluyen ejemplos adicionales que pueden causar este error:

  • Si metadata.event_type es USER_LOGIN y no se configura el campo target.user value de UDM.
  • Si metadata.event_type es NETWORK_CONNECTION y no se configuró el campo target.hostnameUDM.

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

Una opción para solucionar este tipo de error es comenzar por establecer valores estáticos en los campos del UDM. Después de definir todos los campos de UDM necesarios, examina el registro sin procesar original para ver qué valores analizar y guardar en el registro de UDM. Si el registro sin procesar original no contiene ciertos campos, es posible que debas establecer valores predeterminados.

La siguiente es una plantilla de ejemplo, específica para un tipo de evento USER_LOGIN, que ilustra este enfoque.

Ten en cuenta lo siguiente:

  • La plantilla inicializa variables intermedias y establece cada una en una cadena estática.
  • El código de la sección Field Assignment establece los valores en variables intermedias para los campos del UDM.

Puedes expandir este código agregando variables intermedias y campos del UDM adicionales. Después de identificar todos los campos del UDM que se deben completar, haz lo siguiente:

  • En la sección Input Configuration, agrega código que extraiga campos del registro sin procesar original y establezca los valores en las variables intermedias.

  • En la sección Date Extract, agrega código que extraiga la marca de tiempo del evento del registro sin procesar original, la transforme y la establezca en la variable intermedia.

  • Según sea necesario, reemplaza el valor inicializado establecido 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"
    }
  }

}

Analiza texto no estructurado con una función de Grok

Cuando usas una función de Grok para extraer valores de texto no estructurado, puedes usar patrones de Grok predefinidos y expresiones regulares. Los patrones de Grok facilitan la lectura del código. Si la expresión regular no incluye caracteres abreviados (como \w, \s), puedes copiar y pegar la instrucción directamente en el código del analizador.

Dado que los patrones de Grok son una capa de abstracción adicional en la instrucción, pueden hacer que la solución de problemas sea más compleja cuando encuentres un error. A continuación, se muestra un ejemplo de una función de Grok que contiene patrones de Grok predefinidos y 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 de Grok puede tener un mejor rendimiento. Por ejemplo, el siguiente ejemplo requiere menos de la mitad de los pasos de procesamiento para la coincidencia. Esto es importante para una fuente de registros que podría tener un volumen alto.

Comprende las diferencias entre las expresiones regulares de RE2 y PCRE

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

La siguiente es una instrucción PCRE: (?<_custom_field>\w+)\s

La siguiente es una instrucción de RE2 para el código del analizador: (?P<_custom_field>\\w+)\\s

Asegúrate de escapar los caracteres de escape

Las Operaciones de seguridad de Google almacenan los datos de registro sin procesar entrantes en formato codificado en JSON. Esto es para garantizar que las cadenas de caracteres que parecen ser abreviaturas de expresiones regulares se interpreten como la cadena literal. Por ejemplo, \t se interpreta como la cadena literal, en lugar de un carácter de tabulación.

El siguiente ejemplo muestra un registro sin procesar original y el registro con formato codificado en JSON. Observa el carácter de escape que se agregó delante de cada carácter de barra inversa 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 uses una expresión regular en el código del analizador, debes agregar caracteres de escape adicionales si deseas extraer solo el valor. Para que coincida una barra diagonal inversa en el registro sin procesar original, usa cuatro barras diagonales 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>.*)\\\\$

A continuación, se muestra el resultado generado. El grupo con nombre _value almacena el término entry:

"_value": "entry"

Cuando muevas una instrucción de expresión regular estándar al código del analizador, escapa los caracteres abreviados de la expresión regular en la instrucción de extracción. Por ejemplo, cambia \s a \\s.

Dejar sin cambios los caracteres especiales de expresiones regulares cuando se escapan dos veces en la instrucción de extracción Por ejemplo, \\ permanece sin cambios como \\.

La siguiente es una expresión regular estándar:

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

La siguiente expresión regular se modificó para que funcione dentro del código del analizador.

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

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

Expresión regular Se modificó la expresión regular para el código del analizador Descripción del cambio
\s
\\s
Los caracteres abreviados deben escaparse.
\.
\\.
Los caracteres reservados deben escaparse.
\\"
\\\"
Los caracteres reservados deben escaparse.
\]
\\]
Los caracteres reservados deben escaparse.
\|
\\|
Los caracteres reservados deben escaparse.
[^\\]+
[^\\\\]+
Los caracteres especiales dentro de un grupo de clases de caracteres deben incluir un escape.
\\\\
\\\\
Los caracteres especiales fuera de un grupo de clases de caracteres o los caracteres abreviados no requieren un 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 con el siguiente error:

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

Debes agregar un grupo de captura válido a la expresión. Si usas patrones de Grok, estos incluyen un grupo de captura con nombre de forma predeterminada. Cuando uses anulaciones 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>.*$)"

A continuación, se muestra el resultado, que incluye el texto asignado al grupo con nombre _catchall.

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

Usa un grupo con nombre de comodín para comenzar a medida que desarrollas la expresión

Cuando crees una sentencia de extracción, comienza con una expresión que capture más de lo que deseas. Luego, expande la expresión un campo a la vez.

En el siguiente ejemplo, se comienza con un grupo con nombre (_catchall) que coincide con todo el mensaje. Luego, crea la expresión paso a paso haciendo coincidir porciones adicionales del texto. Con cada paso, el grupo con nombre _catchall contiene menos texto original. Continúa y repite el proceso paso a paso para que coincida con el mensaje hasta que ya no necesites el grupo con nombre _catchall.

Paso Expresión regular en el código del analizador Es el 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.

Cómo escapar caracteres abreviados en la expresión regular

Recuerda usar caracteres de escape para las abreviaturas de expresiones regulares cuando uses la expresión en el código del analizador. A continuación, se muestra un ejemplo de cadena de texto 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, el resultado no incluye la letra s.

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

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

Expresión regular revisada para el código del analizador Es el 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. Otros caracteres, como "`", no necesitan más escapes.

Un ejemplo completo

En esta sección, se describen las reglas anteriores como un ejemplo integral. 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 String 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 modificó para que funcione en el código del analizador.

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

¿Necesitas más ayuda? Obtén respuestas de miembros de la comunidad y profesionales de Google SecOps.