¿Cómo agregar el registro a una biblioteca para que pueda integrarse fácilmente con el sistema de registro del programa que utiliza la biblioteca?

9

Estoy escribiendo una biblioteca que tiene mucha información que puede ser útil en un registro en el programa que la usa, pero no sé la mejor manera de exponerla de tal manera que el programa que usa mi biblioteca pueda integrar los registros de mi biblioteca con sus propios registros aparentemente (si lo desea).

Elegir una biblioteca de registro específica para mi biblioteca se agrega a la lista de dependencias para usar mi biblioteca y vincula el programa principal a esa biblioteca, y si varias bibliotecas utilizadas por el programa principal hicieran esto, cada una podría haber seleccionado una biblioteca diferente .

Pensé en hacer que el programa registre un objeto de flujo de C ++ con la biblioteca para que lo use. Parece que sería un propósito relativamente general, pero también pensé en que el programa principal registrara una función de devolución de llamada que se solicitaría con los contenidos y metadatos cuando se registran los datos. Otra opción sería almacenar los datos de registro en la biblioteca en algún tipo de lista para que el programa principal los tome cuando quiera tratar con esos datos, dejando que el programa principal decida cuándo tiene tiempo para manejar los datos.

Estoy buscando sugerencias y pros / contras de diferentes enfoques para poder decidir qué es lo mejor en mi situación.

xaxxon
fuente
1
Realmente no es una respuesta, pero le sugiero que mire cómo lo hace el kit de herramientas Qt. Google QtMessageHandler. Y QMessageLogger. Se parece mucho a lo que sugieren las otras respuestas, por cierto.
Teimpz

Respuestas:

8

Puede exponer varios métodos para recibir el registro desde su biblioteca y envolver todos los adaptadores menos uno en el "real" utilizado en la biblioteca.

Por ejemplo, decide tener internamente una std::function<void(std::string)>colección que es cada devolución de llamada del registrador. Tu provees:

void registerLogCallback(std::function<void(std::string)> callback); 
// Main logging implemention

y también

registerLogStream(std::ostream stream) { 
    registerLogCallback([stream](std::string message){ stream << message; }); 
}

y también

template<typename OutputIterator>
registerLogOutputIterator(OutputIterator iter) { 
    registerLogCallback([iter](std::string message){ *iter++ = message; }); 
}

y más variaciones de los tipos de "recibir cadenas de algún lugar" para las que desea implementar adaptadores.

Caleth
fuente
Creo que eventualmente también necesitaría algún tipo de "nivel de registro", como depuración, advertencia, fatal, ... Para que el usuario pueda filtrar lo que necesita. Buen comienzo, no obstante.
Teimpz
4

La forma más sencilla de permitir que una aplicación pueda acceder a la funcionalidad de registro es permitirle registrar una clase / función para recibir el mensaje de registro. Lo que hacen con ese mensaje depende totalmente de la aplicación.

Con C / C ++, puede usar lo siguiente en su biblioteca:

typedef void (*LogMessageReceiver)(char const* message,
                                   void* user_data);

void registerLogMessageReceiver(LogMessageReceiver receiver,
                                void* user_data);

Una aplicación puede registrar una función llamando registerLogMessageReceiver. con lo apropiado user_data. En la sección de registro de su base de código, debe asegurarse de llamar a esa función con el mensaje apropiado y el registrado user_data.

Si no tiene que preocuparse por C, puede usar una clase como receptor de mensajes.

struct LogMessageReceiver
{
   virtual ~LogMessageReceiver() {}
   virtual void receive(std::string const& message) = 0;
};

y agregue una función en la biblioteca para permitir que una aplicación registre un receptor de mensajes de registro.

void registerLogMessageReceiver(LogMessageReceiver* receiver);

Una aplicación puede registrar un LogMessageReceiverllamando a la función anterior. Deberá tomar algunas decisiones de política con respecto a la propiedad de los registrados LogMessageReceiver. Si la biblioteca toma posesión del receptor, debe ser deleteel puntero. Si la biblioteca no toma posesión del receptor, la aplicación debe cuidar deleteel receptor.

El uso de una clase como el receptor de mensajes de registro permite user_dataque se omita el bit registerLogMessageReceiverya que el subtipo de LogMessageReceiveres libre de contener cualquier dato que sea útil para su funcionamiento. No necesita pasar ningún dato de usuario adicional en la receivefunción.

A partir de ahí, puede volverse más complejo dependiendo de cuán sofisticado sea su mecanismo de registro.

Por ejemplo, podría tener varios niveles de registro: Conciso, Normal, Detallado o LoggingLevel1, LoggingLevel2, ..., LoggingLevelN.

En ese caso, deberá permitir que la aplicación controle el nivel de registro que desea utilizar.

Hay un conjunto interminable de opciones una vez que decide ir más allá del mecanismo de registro simplista. No tiene sentido profundizar en ellos aquí.

R Sahu
fuente
Esta es una buena respuesta si los compiladores anteriores a C ++ 11 necesitan ser compatibles. Si no, favorecería la respuesta de Caleths. std :: function permite capturar, que es una alternativa mucho más segura al vacío *
Teimpz
1

Yo afirmaría que debería repensar la necesidad de tener un registro junto con su biblioteca; Especialmente para C ++ donde no hay una interfaz de registrador estándar.

Las diferentes aplicaciones tienen diferentes políticas con respecto al registro. Una biblioteca debe ser independiente de las políticas.

El propósito de una biblioteca es proporcionar un servicio y, preferiblemente, indicar si una solicitud de ese servicio tuvo éxito o no; idealmente con una indicación de por qué [falló] a través de errno, código de retorno, excepción ... Si su deseo de iniciar sesión se debe a que una función proporcionada puede fallar en varios lugares, podría estar intentando hacer demasiado en una función. Quizás no , pero considere la posibilidad.

Daniel
fuente
@xaxxon No veo por qué esto no es una respuesta. Según ¿Cómo escribo una buena respuesta? an [...] answer can be “don’t do that" [...].
doubleYou
1

Escribir una biblioteca que interactúe fácilmente con el sistema de registro de la aplicación host es fácil, siempre que sepa qué es ese sistema. Cuando hay múltiples posibilidades para eso, es más difícil y estás limitado por la tiranía del mínimo común denominador. Podría tener una capa de compatibilidad que adapte su biblioteca a los diversos sistemas de registro, pero ahora no puede confiar en alguna característica útil y única que solo tiene un sistema. Si el usuario final tiene el otro sistema, esa útil característica única no está allí.

En el mundo .Net, hay (al menos) dos sistemas de registro de uso común, log4net y NLog. Ambos son similares, pero no idénticos. Ya estaba usando log4net, y luego comencé a usar NHibernate (una biblioteca ORM). Afortunadamente, utiliza log4net internamente, por lo que fue fácil agregarlo a un proyecto. (Y aquí no estoy de acuerdo con la respuesta de @Daniel: a veces es tremendamente útil para NHibernate registrar su actividad con gran detalle en el mismo sistema que estoy usando en otro lugar, sin trabajo adicional). Si ya estaba invertido en NLog, eso significa que Me cambio, o tengo un proyecto que usa ambos.

Al menos con el registro, generalmente existe la opción de crear un anexo de mensaje personalizado que puede configurar para reenviar un mensaje registrado en un sistema al otro sistema de registro. Entonces, si ya estaba usando NLog y realmente quería continuar con él, pero también usaba NHibernate, podría apretar los dientes y escribir un apéndice log4net que reenvíe cada mensaje a NLog. Sería sencillo hacerlo si las API son similares.

Las alternativas son realmente elegir el mejor sistema para sus necesidades que esté disponible y usarlo sin reservas, o tener algún tipo de capa adaptadora, que esté sujeta al problema del mínimo común denominador. Lo cual no es una respuesta muy satisfactoria.

Carl Raymond
fuente
Esto, más el hecho de que C ++ hace que las cosas fáciles sean difíciles y las cosas difíciles aún más difíciles.
rwong