Saltar al contenido

Etiqueta: logging

Logging en Cloudwatch (Amazon Web Services)

Hace un tiempo en el trabajo tuve la oportunidad de ser parte de un proyecto que buscaba centralizar el logging de múltiples aplicaciones en una sola y persistir esta información no en una base de datos, sino en uno de los servició que ofrece Amazon Web Services (AWS) para este fin que es CloudWatch. Esto me llevo a todo un proceso de investigación y recabar información sobre la mejor manera de realizar este grabado de log. De ese proceso de investigación nace este post.

Primero, hay que decir que AWS es todo un mundo por si solo, se tienen muchos servicios orientados a necesidades especificas en el proceso de desarrollo de software, asi como también de cara al despliegue de aplicaciones, seguridad, networking, almacenamiento, entre otros. Esto me lleva a comentarles que en adelante intentaré realizar más post sobre los servicios que ofrecen y como se pueden usar, esto obviamente lo hago para compartir la información y también afianzar lo poco o mucho que puedo ir aprendiendo. Vamos al tema en específico, cuando me planteo realizar esta aplicación, que por lo demás tenía claro que era un API en net core, busque que alternativas tengo para comunicarme con AWS y específicamente con cloudwatch, encontrando las siguientes:

  • SDK de Amazon
  • Diversas librerias que se integran con aplicaciones de loggers (Nlog, Log4net, Serilog, etc).

Debido al tiempo que tenía para realizar la aplicación y a la experiencia que tenía con las herramientas que tenía a la mano, termine decantandome por usar la librería para Nlog. Esto, por que ya tenía el conocimiento de Nlog, te instala un módulo que le permite tener acceso a cierta información para grabar un log. Sin embargo, esto me trajo algunas complicaciones para el caso de net core que ya más adelante veremos.

Proyecto

La demo, consta de una aplicación web api generada de 0, con lo mímimo necesario apra que pueda funcionar nuestro log en AWS. Las librerías a usar, son las siguientes:

  • AWS.Logger.NLog (v1.5.1)
  • NLog (v4.6.8)

Me interesa explicar como se lleva a cabo esta configuración, dado que lo demás es lo típico con cualquier aplicación en net core. En nuestro archivo “Startup.cs” tenemos que realizar las siguientes configuraciones:

Configuración del target en nlog para AWS

Config target

Como se puede ver, se crea una nueva clase del target (que esta disponible luego de instalar la librería) y tenemos que proporciarle cierta información:

  • Credentials: Se le indica el tipo de autenticación que se va a usar, para la demo he creado en AWS un IAM (manejo de indentidad) que sólo tenga permisos para poder comunicarse con cloudwatch (en otro post voy a ahondar en como AWS maneja la seguridad), existen otras tipos de credenciales, por ejemplo en el trabajo dado que nuestro aplicativo está en un EC2 se puede obtener la autenticación de la máquina.
  • LogGroup: Cada uno de los log que se graben están asociados a un log group, que digamos que es la instancia que se crea para almacenar todos los logs que provengan de ese cliente.
  • Region: Es la región en la cual está nuestra cuenta de AWS. Entendamos que esta plataforma esta mundial, y por lo tanto tiene servidores en distintos lugares del mundo, esto debido a buscar que sus servicios sean escalables y redundantes ante cualquier eventualidad y a ti como usuario te permite elegir consumir los servicios que ofrecen en distintos lugares del mundo. De esto también se puede ahondar más adelante.
  • LogStreamNameSuffix y LogStreamNamePrefix : Cuando se crea una nueva instancia en un log group esta se crea con un identificador único la cual es un GUID más un timestamp, esto es así si no se le proporciona ningún parámetro. En cambio si le pasamos un sufijo o un prefijo ya no se genera el GUID, sino se le concatena a lo establecido (prefijo y sufijo) la marca de tiempo.
  • Layout: Este punto lo deje al final por que es donde probablemente me deba explayar más. como ven es un texto, pero me estoy valiendo de los layouts renders proporcionados por nlog para obtener cierta información y evitarme tener que obtenerla por otros medios.

Ampliando el último punto, pueden encontrar más información sobre esto en este link.

Una ves que se configura el target de AWS, es necesario indicarle a nlog que debe de usarlo, asociarle los eventos del log y la configuración a logmanager. Veamos como se hace esto en código.

Configuración de nlog

Como se puede ver, se crea el un nuevo “LoggingConfiguration”, a este se le asocia el target a aws creado y además las reglas a las cuales estará vinculado nuestro target, finalmente también estoy indicando donde estará el log interno de nlog y le paso toda está configuración a logmanager.

Para cierta información que en teoría debería ser proporcionada por los layout render, pero que parece que en net core no funcionan correctamente, me valí de un Middleware, acá pueden ver que información de obtiene de ahí.

Configuración del middleware

Se está obteniendo la URL que se está consumiendo, el username que consume la aplicación, si se tuviera como header, el método http (PUT, POST, GET, etc). Finalmente, cuando un evento de log (para nuestra demo excepción) se lleva a cabo.

Evento de error disparándose
Disparando el log

Finalmente, veamos nuestro appsettings para ver que configuración tenemos ahí.

Configuración del appsetting

El accesskey y secretkey son las que cree para la demo, este usuario ya ha sido eliminado de mi cuenta de aws.

Realicemos la prueba de nuestra demo para ver si efectivamente tenemos nuestro log en AWS ahora. Primero tomemos algunas capturas del ambiente antes de ejecutar nuestra demo.

Log Groups en AWS

Listado de log groups

Postman

Get request

Levantamos la solución (con lo que nuestro log group ya debería haberse creado en AWS) y realizamos la solicitud al api.

Entro a la excepción
Esta por enviarse el log a AWS
Respuesta en postman
Validamos que se creo nuestro log group
Validamos nuestro log stream
El log grabado

Como podemos ver (si hacemos zoom) nuestro log se ha grabado correctamente en AWS.

Con esto llegamos al fin del post, dejo el link al repositorio donde está el código fuente por si quieres revisarlo y un video de la demo.


REPO: GITHUB

Saludos.

2 comentarios

Buenas Practicas Logging

En este post, me dedicaré a explicar que buenas practicas en logging se deben seguir cuando desarrollamos una aplicación web, que quede claro que este es un enfoque personal, que he podido formular con cierta experiencia.

Por otro lado, es necesario comentar que si bien es cierto el ejemplo en cuestión, lo voy a realizar usando Spring boot, la información que se debe obtener para enriquecer el log, puede variar de acuerdo a la tecnología que se va a usar (Net Core, Framework, Node, etc).

Empecemos primero, explicando un poco sobre los niveles de logging que se tienen y en qué caso es necesario usar uno u otro.

Niveles de Logging

Como se puede ver en la imagen, el logging no solo sirve para capturar errores (lo cual es fundamental en el proceso de desarrollo y para el posterior soporte a las aplicaciones), sino también para determinar que feature es el más usado, información de configuración de la aplicación, entre otras.

Por otro lado, el logging se puede desarrollar en múltiples appenders (este nombre es usado en log4j o log4net), como una buena práctica es necesario que como mínimo se realice el grabado del log en dos appenders, siendo una de estos la base de datos y el otro un archivo de texto, en una ruta en específico (pudiendo ser dentro de la solución desplegada). Basicamente en este post me enfocaré en explicar la información que se debe guardar en un log de error (FATAL y ERROR). La información que vamos a considerar es la siguiente:

  • Fecha y hora del log
  • Nivel del log (FATAL, WARNING, etc)
  • Mensaje
  • Stack de la excepción
  • Payload (en caso que la consulta sea un GET, DELETE se debe obtener la información enviada en el url, de ser un POST, UPDATE, PATCH El body)
  • Callsite (el endpoint que es consumido que en el cual se termina generando el error)
  • La acción bajo la cual se produce el error (nombre del método en el controlador)
  • El usuario con el cual se produjo el error.
  • El nombre del método en el cual se generó el error (de la capa de lógica de negocio)
  • El nombre de la aplicación en la cual se generar el error (varía de solución en solución)

Claro, que de acuerdo a qué librería se este usando para realizar este grabado del log es posible tener todo tipo de appenders, uno que me llama mucho la atención es el que nos permite enviar esta información a herramientas como elastic search y luego al integrarlo con otras herramientas como grafana para generar reportes.

En nuestra aplicación demo, nos valdremos que ciertas características del framework para poder tener acceso a la información antes detallada.

HandlerInterceptorAdapter

Al implementar esta clase abstracta y hacer un override al método preHandle tenemos acceso en el pipeline del framework, además de anotarla como “Component”. Como podemos ver en la siguiente imagen.

Implementación del override del preHandle

Como podemos ver, nos valemos del HttpServletRequest para tener acceso a mucha información necesaria para enriquecer nuestro logging, detallemos cada uno de los puntos que nos servirán:

  • La url a la cual se está consultando la obtenemos con el método “getRequestURI”.
  • Para obtener el nombre del método, nos valemos de una pequeña lógica que obtiene el indice del último luego del “/” y con esto hace un substring para poder obtener el último texto.
  • Para el hostname de la máquina, nos valemos de la librería “java.net.InetAddress”.
  • Es necesario validar si la petición es del tipo “GET” para de este modo obtener los query params que se esten enviando. Si este fuera el caso usamos “getQueryString” para este fin.
  • Obtenemos un parámetro denominado “username” que se está enviando en cada una de las peticiones, sea cual sea el método http, esto con la finalidad de guardar el usuario al cual está vinculado el log. Para esto tenemos opción de guardarlo en sesión en el caso nuestra solución sea del tipo MVC, pero al tener un Web API no es posible. Podrían usar un interceptos en angular (de ser este su caso) para que a cada una de las peticiones le adicione este parámetro.

Con esto, cubrimos en cierto aspecto la información que necesitamos para nuestro logging. Sin embargo, tenemos que considerar también las solicitudes http del tipo POST, y que por lo tanto tienen un body.

RequestBodyAdviceAdapter

Nos valdremos de esta clase abstracta, y especificamente en nuestra implementación haremos el override de “supports” y de “affterBodyRead” como podemos ver en la siguiente imagen:

Override de métodos en la implementación de HandlerInterceptorAdapter

Además, es necesario decorar la clase con la etiqueta “ControllerAdvice”. Veamos que información obtenemos de este método:

  • El payload del body, el cual obtenemos del Object que se recibe de “afterBodyRead”, en mi caso se hace un “toString” dado que en las clases implementadas se está haciendo un override a esté metodo para darle la forma de json, como podemos ver en la siguiente imágen.
Override del “toString” en la clase

Además, es muy importante en nuestro logging tener grabadas en nombre del método en el cual se realizo la caída del sistema. Este método puede estar en el stack de la excepción, pero para tenerlo directamente me estoy valiendo de una característica propia del framework, la cual se denomina “aspects” en spring, en algún otro post me dedicaré a explicar los tipos que hay y como se ejecutan en detalle, en este post sólo presentaré como se están usando.

Aspects

Spring nos brinda una herramienta muy potente, la cual es denominada programación orientada aspectos, lo cual en pocas palabras, puede facilitarnos la vida en múltiples escenarios y evitarnos repetir código innecesariamente. Veamos la siguiente imagen:

Implementación de un aspecto

Se creo un aspecto y se decoro con las etiquetas “Aspect” y “Component” para que se registre por el framework, lo que nos permite un aspecto es que esta sección de código de ejecute en ciertas condiciones. Para el caso, se llevará a cabo en la siguiente sintaxis:

“* com.project.loggingdoitright.service.implementation.*.*(..)”

Lo cual nos indica que el aspecto coberturará todos los métodos que se encuentren en el paquete “implementation” que tengan cualquier nombre (veamos los *.*), que tengan cualquier tipo de entrada (veamos los dos puntos dentro de los paréntesis) y el asterisco inicial, indica que coberturará cualquier tipo de devolución. Como variable de entrada de nuestros aspectos tenemos a la clase “JoinPoint”, la cual posee varia información interesante, para lo que necesitamos usaremos “getSignature().getName()” que nos brinda el nombre del método en el cual se registra el error y el cual llama nuestro aspecto.

Finalmente, para realizar el grabado del log y generar un identificador único del log, el cual debe ser proporcionado al usuario del aplicativo. Para esto, usamos algo que ya se explicado antes. Un “ControllerAdvice”, el cual actua como interceptor de los errores.

Finalmente, veamos como queda nuestro registro del log.

Divido la fila asi, para que se pueda visualizar mejor.

Finalmente, puede visualizar el video correspondiente a este post, donde se detalla paso a paso el funcionamiento de la aplicación, así como su prueba consumiento las APIs desde postman.

Espero que les haya sido de ayuda, como siempre dejo el código del proyecto en el repositorio.

REPO: GITHUB

Saludos.

Deja un comentario

Grabar log de errores en db y archivo con Spring Boot

Llevo ya algunas semanas revisando cursos sobre Spring, como he mencionado en publicaciones anteriores, el trabajo me lleva a desarrollar más en tecnologías de Microsoft. Sin embargo, conforme voy aprendido más de spring, me interesa más por lo pontente que es y el beneficio que brinda al evitar la repetición de código que no le suma nada a la lógica de negocio o ”  boilerplate code “.

El post de hoy, lo centrare en el grabado de logging cuando tenemos una aplicación hecha con spring boot, como saben el log es fundamental dado que un buen log nos permite resolver los problemas de nuestro aplicativo sin mucha demora, en un post posterior explicare lineamientos para generar un buen log y buenas practicas en el mismo. Sin embargo me interesa hacer un post de esto acá porque he tenido que revisar mucho la documentación para poder configurar el log como lo necesitaba en spring y no pude encontrar ningún lugar donde expliquen detenidamente como poder realizar esto.

El log que grabaremos se realizará en base de datos (postgres) y un archivo de texto (.txt), se pueden tener multiples tipos (niveles) de log, que nos permiten medir la usabilidad de los features, los errores y las caidas fatales del sistema, para la demo me enfocare en los errores y explicare como en spring podemos capturar todos los errores que se generen, grabarlos en base de datos y el archivo de texto y al usuario mostrarle un mensaje de texto genérico y un código vinculado a nuestro registro de error.

APLICATIVO

Estoy usando Intellij IDEA como IDE para el desarrollo de esta demo, y la aplicación se divide en repository, service y controller. Considerando que el repository y service tenemos una implementación para las interfaces que definimos. Además, en repository tenemos una capa para hacer el mapping a entidades de lo recibido por la base de datos.

Project files de Spring boot

Para grabar los logs, usar log4j2 que es una librería de amplia trayectoria y bien documentada, para configurarla en la sección de resources de nuestra aplicación es necesario crear un archivo de configuración, este puede ser un xml, json o un properties. En mi caso cree un “log4j2.properties” .

Archivo de configuración del log

En el properties de spring es necesario indicarle que tenemos un archivo de configuración de log con las siguientes sentencias:

Configuración en el application.properties para el log

En el log4j2.properties que hemos creado es necesario indicar la siguiente configuración.

Configuración del log en log4j2.properties

En esta estamos indicando el indicando al configuración de la base de datos, es necesario indicar cada columna de la tabla que hemos destinado para los logs en base de datos con los valores que se van a considerar para cada columna. También está la configuración para el archvo de texto, finalmente se indica que ambos appender deben ser utilizados y el nivel mínimo del log.

También es posible indicar en el log variables configuradas por nosotros, para mi caso es necesario saber el endpoint que se está consumiendo, el nombre del método del endpoint, el nombre del método donde el error ocurrio, un guid para vincular el error a un identificador único entre otros valores. Esto podemos conseguirlo seteando variables como propiedades del sistema y luego leyendo en nuestro log4j2.propierties.

Seteo de variables como propiedades del sistema
Obtenemos la propiedad del sistema en log4j2.properties

Una explicación didáctica de esta configuración así como algunas cosas que no pongo en el post para no hacerlo muy extenso lo encuentran en el video. Quedan muchos puntos por detallar más que desarrolloraré en otros posts, espero que les haya sido de utilidad y como siempre dejo los links al repositorio para que revisen ustedes el código.

REPO: GITHUB

Saludos.

Deja un comentario