Mensajes de logging (registro)

El registro es la forma más común de comunicar al usuario información actual sobre el funcionamiento del programa. Dicha información puede consistir en el estado de una realización regular, de una indicación de progreso durante un cálculo largo o de datos de depuración para encontrar y reproducir errores.

Por desgracia, ningún programador es inmune a que haya errores en su código. Por ello, los desarrolladores suelen intentar dejar el denominado «rastro de migas de pan»: registrar las principales etapas de ejecución del programa (al menos, la secuencia de llamadas a funciones).

Ya conocemos dos funciones de registro: Print y PrintFormat. Las hemos utilizado en los ejemplos de las secciones anteriores. Tuvimos que emplearlas con antelación de un modo simplificado, ya que es casi imposible prescindir de ellas.

Una llamada a una función genera, por regla general, un registro. No obstante, si se encuentra un carácter de nueva línea ('\n') en la cadena de salida, dividirá la información en dos partes.

Tenga en cuenta que todas las llamadas a Print y PrintFormat se transforman en entradas de registro en la pestaña Experts de la ventana Toolbox. Aunque la pestaña se llama Experts, recopila los resultados de todas las instrucciones de impresión, con independencia del tipo de programa MQL.

Los registros se almacenan en archivos organizados según el principio «un día = un archivo»: tienen los nombres AAAAMMDD.log (A por año, M por mes y D por día). Los archivos se encuentran en <data directory>/MQL5/Logs (no los confunda con los registros del sistema del terminal en la carpeta <data directory>/Logs).

Tenga en cuenta que, durante el registro masivo (si las llamadas a la función Print generan una gran cantidad de información en poco tiempo), el terminal sólo muestra algunas entradas en la ventana. Esto se hace para optimizar el rendimiento. Además, en cualquier caso, el usuario no puede ver todos los mensajes sobre la marcha. Para ver la versión completa del registro, debe ejecutar el comando Ver del menú contextual. Como resultado, se abrirá una ventana con un registro.
 
También hay que tener en cuenta que la información del registro se almacena en caché cuando se escribe en el disco, es decir, se escribe en los archivos en grandes bloques de modo perezoso, razón por la cual en un momento dado el archivo de registro, por regla general, no contiene las entradas más recientes (aunque sean visibles en una ventana). Para iniciar un vaciado de caché en el disco puede ejecutar el comando View o Open en el menú contextual del registro.

Cada entrada de registro va precedida de una hora con una precisión de milisegundos, así como del nombre del programa (y sus gráficos) que ha generado o causado este mensaje.

 

void Print(argument, ...)

La función imprime uno o más valores en el registro de expertos, en una línea (si los datos de salida no contienen el carácter '\n').

Los argumentos pueden ser de cualquier tipo integrado. Se separan por comas. El número de parámetros no puede ser superior a 64. Su número variable se indica mediante una elipsis en el prototipo, pero MQL5 no permite describir funciones propias con una característica similar: sólo algunas funciones integradas de la API tienen un número variable de parámetros (en particular, StringFormat, Print, PrintFormat y Comment).

Para estructuras y clases, debe implementar un método de impresión integrado, o mostrar sus campos por separado.

Además, la función no es capaz de manejar arrays. Puede visualizarlos elemento por elemento, o utilizar la función ArrayPrint.

Los valores del tipo double son mostrados por la función con una precisión de hasta 16 dígitos significativos (juntos en la mantisa y la parte fraccionaria). Un número puede mostrarse en formato tradicional o científico (con un exponente), lo que resulte más compacto. Los valores del tipo float se muestran con una precisión de 7 decimales. Para mostrar números reales con una precisión diferente, o para especificar explícitamente el formato, debe utilizar la función PrintFormat.

Los valores de tipo bool se muestran como cadenas «true» o «false».

Las fechas se muestran con el día y la hora especificados con la máxima precisión (hasta un segundo), en el formato «AAAA.MM.DD hh:mm:ss». Para visualizar la fecha en un formato diferente, utilice la función TimeToString (véase el apartado Fecha y hora).

Los valores de enumeración se muestran como números enteros. Para mostrar los nombres de los elementos, utilice la función EnumToString (véase la sección Enumeraciones).

Los caracteres de uno y dos bytes también se muestran como números enteros. Para visualizar símbolos como caracteres o letras, utilice las funciones CharToString o ShortToString; véase la sección Trabajar con símbolos y páginas de códigos).

Los valores del tipo color se muestran como una cadena con un triple de números que indican la intensidad de cada componente de color («R, G, B») o como un nombre de color si dicho color está presente en el conjunto de colores.

Para obtener más información sobre la conversión de valores de distintos tipos a cadenas, véase el capítulo Conversión de datos de tipos integrados (especialmente en las secciones De números a cadenas y viceversa, Fecha y hora, Color).

Cuando se trabaja en el comprobador de estrategias en modo de una sola pasada (probar Asesor Experto o indicador), los resultados de la función Print se envían al registro del agente de pruebas.

Cuando se trabaja en el probador de estrategias en el modo de optimización, el registro se suprime por razones de rendimiento, por lo que la función Print no tiene ningún efecto visible. Sin embargo, se evalúan todas las expresiones dadas como argumentos.

Todos los argumentos, una vez convertidos a una representación de cadena, se concatenan en una cadena común sin caracteres delimitadores. Si es necesario, dichos caracteres deben escribirse explícitamente en la lista de argumentos. Por ejemplo:

int x;
bool y;
datetime z;
...
Print(x", "y", "z);

Aquí se registran 3 variables, separadas por comas. Si no fuera por los literales intermedios «,», los valores de las variables estarían pegados en la entrada del registro.

Se pueden encontrar muchos casos de aplicación de Print ya en las primeras secciones del libro (por ejemplo, Primer programa, Asignación e inicialización, expresiones y arrays, entre otros).

Como una nueva forma de trabajar con Print implementaremos una clase simple que le permitirá mostrar una secuencia de valores arbitrarios sin especificar un carácter separador entre cada valor vecino. Utilizamos el enfoque de sobrecarga del operador '<<', similar a lo que se utiliza en los flujos de E/S de C++ (std::cout).

La definición de la clase se colocará en un archivo de encabezado independiente OutputStream.mqh. A continuación se muestra una clase de forma simplificada.

class OutputStream
{
protected:
   ushort delimiter;
   string line;
   
   // add the next argument, separated by a separator (if any)
   void appendWithDelimiter(const string v)
   {
      line += v;
      if(delimiter != 0)
      {
         line += ShortToString(delimiter);
      }
   }
   
public:
   OutputStream(ushort d = 0): delimiter(d) { }
   
   template<typename T>
   OutputStream *operator<<(const T v)
   {
      appendWithDelimiter((string)v);
      return &this;
   }
   
   OutputStream *operator<<(OutputStream &self)
   {
      if(&this == &self)
      {
         print(line);// output of the composed string
         line = NULL;
      }
      return &this;
   }
};

Su objetivo es acumular en una variable de cadena line representaciones de cadena de cualquier argumento pasado utilizando el operador '<<'. Si se especifica un carácter separador en el constructor de la clase, se insertará automáticamente entre los argumentos. Dado que el operador sobrecargado devuelve un puntero a un objeto, podemos pasar en cadena una secuencia de argumentos:

OutputStream out(',');
out << x << y << z << out;

Como atributo del final de la recogida de datos, y para la salida real del contenido line en el registro, se utiliza una sobrecarga del mismo operador para el objeto en sí.

La clase real es algo más complicada. En concreto, permite establecer no sólo el carácter separador, sino también la precisión de la visualización de números reales, así como banderas para seleccionar campos en valores de fecha y hora. Además, la clase admite la impresión de caracteres, ushort, en forma de caracteres (en lugar de códigos enteros), la salida simplificada de arrays (en una cadena separada), colores en formato hexadecimal como un único valor (y no un triple de números separados por comas, ya que la coma se utiliza a menudo como carácter separador, y entonces los componentes de color en el registro parecen 3 variables diferentes).

En el script OutputStream.mq5 se ofrece una demostración del uso de la clase.

void OnStart()
{
   OutputStream os(5, ',');
   
   bool b = true;
   datetime dt = TimeCurrent();
   color clr = C'127128129';
   int array[] = {1000, -100};
   os << M_PI << "text" << clrBlue << b << array << dt << clr << '@' << os;
   
   /*
      output example
      
      3.14159,text,clrBlue,true
      [100,0,-100]
      2021.09.07 17:38,clr7F8081,@
   */
}

 

void PrintFormat(const string format, ...) ≡ void printf(const string format, ...)

La función registra un conjunto de argumentos basados en la cadena de formato especificada. El parámetro format no sólo proporciona una plantilla de cadena de salida de texto libre que se muestra «tal cual», sino que también puede contener secuencias de escape que describen cómo deben formatearse argumentos específicos.

El número total de parámetros, incluida la cadena de formato, no puede ser superior a 64. Las restricciones sobre los tipos de parámetros son similares a las de las funciones print.

PrintFormat Los principios de funcionamiento y de formato son idénticos a los descritos para la función StringFormat (véase la sección Salida universal de datos formateados a una cadena). La única diferencia es que StringFormat devuelve la cadena formada al código de llamada, y print format la envía al diario. Podemos decir que PrintFormat tiene el siguiente equivalente condicional:

Print(StringFormat(<list of arguments as isincluding format>))

Además del nombre completo PrintFormat puede utilizar un alias más corto printf.

Al igual que la función Print, PrintFormat tiene algunas características específicas cuando se trabaja en el probador en el modo de optimización: su salida al registro se suprime para mejorar el rendimiento.

Ya hemos conocido en muchas secciones scripts que utilizan PrintFormat, como por ejemplo, Transición return, Color, Arrays dinámicos, Gestión de descriptores de archivos, Obtener una lista de variables globales.