Add API to store the logs in files and to upload them on a server.

This commit is contained in:
Ghislain MARY 2014-10-06 11:45:27 +02:00
parent e5311281fc
commit 1d10e749b5
3 changed files with 331 additions and 6 deletions

View file

@ -24,6 +24,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "quality_reporting.h"
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ortp/telephonyevents.h>
#include <ortp/zrtp.h>
#include "mediastreamer2/mediastream.h"
@ -64,6 +66,9 @@ static const char *liblinphone_version=
LIBLINPHONE_VERSION
#endif
;
static OrtpLogFunc liblinphone_log_func = NULL;
static bool_t liblinphone_log_collection_enabled = FALSE;
static const char * liblinphone_log_collection_path = ".";
static bool_t liblinphone_serialize_logs = FALSE;
static void set_network_reachable(LinphoneCore* lc,bool_t isReachable, time_t curtime);
static void linphone_core_run_hooks(LinphoneCore *lc);
@ -127,7 +132,8 @@ const LinphoneAddress *linphone_core_get_current_call_remote_address(struct _Lin
}
void linphone_core_set_log_handler(OrtpLogFunc logfunc) {
ortp_set_log_handler(logfunc);
liblinphone_log_func = logfunc;
ortp_set_log_handler(liblinphone_log_func);
}
void linphone_core_set_log_file(FILE *file) {
@ -144,6 +150,262 @@ void linphone_core_set_log_level(OrtpLogLevel loglevel) {
}
}
#define LOGFILE_MAXSIZE (10 * 1024 * 1024)
static void linphone_core_log_collection_handler(OrtpLogLevel level, const char *fmt, va_list args) {
const char *lname="undef";
char *msg;
char *log_filename1;
char *log_filename2;
FILE *log_file;
struct timeval tp;
struct tm *lt;
time_t tt;
struct stat statbuf;
if (liblinphone_log_func != NULL) {
liblinphone_log_func(level, fmt, args);
}
ortp_gettimeofday(&tp, NULL);
tt = (time_t)tp.tv_sec;
lt = localtime((const time_t*)&tt);
switch(level){
case ORTP_DEBUG:
lname = "DEBUG";
break;
case ORTP_MESSAGE:
lname = "MESSAGE";
break;
case ORTP_WARNING:
lname = "WARNING";
break;
case ORTP_ERROR:
lname = "ERROR";
break;
case ORTP_FATAL:
lname = "FATAL";
break;
default:
ortp_fatal("Bad level !");
}
msg = ortp_strdup_vprintf(fmt, args);
log_filename1 = ortp_strdup_printf("%s/%s", liblinphone_log_collection_path, "linphone1.log");
log_filename2 = ortp_strdup_printf("%s/%s", liblinphone_log_collection_path, "linphone2.log");
log_file = fopen(log_filename1, "a");
fstat(fileno(log_file), &statbuf);
if (statbuf.st_size > LOGFILE_MAXSIZE) {
fclose(log_file);
log_file = fopen(log_filename2, "a");
fstat(fileno(log_file), &statbuf);
if (statbuf.st_size > LOGFILE_MAXSIZE) {
fclose(log_file);
unlink(log_filename1);
rename(log_filename2, log_filename1);
log_file = fopen(log_filename2, "a");
}
}
fprintf(log_file,"%i-%.2i-%.2i %.2i:%.2i:%.2i:%.3i %s %s\n",
1900 + lt->tm_year, lt->tm_mon + 1, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec, (int)(tp.tv_usec / 1000), lname, msg);
fflush(log_file);
fclose(log_file);
ortp_free(log_filename1);
ortp_free(log_filename2);
ortp_free(msg);
}
void linphone_core_set_log_collection_path(const char *path) {
liblinphone_log_collection_path = path;
}
const char *linphone_core_get_log_collection_upload_server_url(LinphoneCore *core) {
return lp_config_get_string(core->config, "misc", "log_collection_upload_server_url", NULL);
}
void linphone_core_set_log_collection_upload_server_url(LinphoneCore *core, const char *server_url) {
lp_config_set_string(core->config, "misc", "log_collection_upload_server_url", server_url);
}
void linphone_core_enable_log_collection(bool_t enable) {
liblinphone_log_collection_enabled = enable;
if (liblinphone_log_collection_enabled == TRUE) {
ortp_set_log_handler(linphone_core_log_collection_handler);
} else {
ortp_set_log_handler(liblinphone_log_func);
}
}
static void process_io_error_upload_log_collection(void *data, const belle_sip_io_error_event_t *event) {
LinphoneCore *core = (LinphoneCore *)data;
ms_error("I/O Error during log collection upload to %s", linphone_core_get_log_collection_upload_server_url(core));
linphone_core_notify_log_collection_upload_state_changed(core, LinphoneCoreLogCollectionUploadStateNotDelivered, "I/O Error");
}
static void process_auth_requested_upload_log_collection(void *data, belle_sip_auth_event_t *event) {
LinphoneCore *core = (LinphoneCore *)data;
ms_error("Error during log collection upload: auth requested to connect %s", linphone_core_get_log_collection_upload_server_url(core));
linphone_core_notify_log_collection_upload_state_changed(core, LinphoneCoreLogCollectionUploadStateNotDelivered, "Auth requested");
}
extern const char *multipart_boundary;
/**
* Callback called when posting a log collection file to server (following rcs5.1 recommendation)
*
* @param[in] bh The body handler
* @param[in] msg The belle sip message
* @param[in] data The user data associated with the handler, contains the LinphoneCore object
* @param[in] offset The current position in the input buffer
* @param[in] buffer The ouput buffer where to copy the data to be uploaded
* @param[in,out] size The size in byte of the data requested, as output it will contain the effective copied size
*
*/
static int log_collection_upload_on_send_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t *msg, void *data, size_t offset, uint8_t *buffer, size_t *size) {
LinphoneCore *core = (LinphoneCore *)data;
char *buf = (char *)buffer;
/* If we've not reach the end of file yet, fill the buffer with more data */
if (offset < core->log_collection_upload_information->size) {
char *log_filename = ortp_strdup_printf("%s/%s", liblinphone_log_collection_path, "linphone1.log");
FILE *log_file = fopen(log_filename, "r");
fseek(log_file, offset, SEEK_SET);
*size = fread(buffer, 1, *size, log_file);
fclose(log_file);
ortp_free(log_filename);
}
return BELLE_SIP_CONTINUE;
}
/**
* Callback called during upload of a log collection to server.
* It is just forwarding the call and some parameters to the vtable defined callback.
*/
static void log_collection_upload_on_progress(belle_sip_body_handler_t *bh, belle_sip_message_t *msg, void *data, size_t offset, size_t total) {
LinphoneCore *core = (LinphoneCore *)data;
linphone_core_notify_log_collection_upload_progress_indication(core, (size_t)(((double)offset / (double)total) * 100.0));
}
/**
* Callback function called when we have a response from server during the upload of the log collection to the server (rcs5.1 recommandation)
* Note: The first post is empty and the server shall reply a 204 (No content) message, this will trigger a new post request to the server
* to upload the file. The server response to this second post is processed by this same function
*
* @param[in] data The user-defined pointer associated with the request, it contains the LinphoneCore object
* @param[in] event The response from server
*/
static void process_response_from_post_file_log_collection(void *data, const belle_http_response_event_t *event) {
LinphoneCore *core = (LinphoneCore *)data;
/* Check the answer code */
if (event->response) {
int code = belle_http_response_get_status_code(event->response);
if (code == 204) { /* This is the reply to the first post to the server - an empty file */
/* Start uploading the file */
belle_http_request_listener_callbacks_t cbs = { 0 };
belle_http_request_listener_t *l;
belle_generic_uri_t *uri;
belle_http_request_t *req;
belle_sip_multipart_body_handler_t *bh;
char* ua;
char *content_type;
char *first_part_header;
belle_sip_user_body_handler_t *first_part_bh;
linphone_core_notify_log_collection_upload_state_changed(core, LinphoneCoreLogCollectionUploadStateInProgress, NULL);
/* Temporary storage for the Content-disposition header value */
first_part_header = belle_sip_strdup_printf("form-data; name=\"File\"; filename=\"%s\"", core->log_collection_upload_information->name);
/* Create a user body handler to take care of the file and add the content disposition and content-type headers */
first_part_bh = belle_sip_user_body_handler_new(core->log_collection_upload_information->size, NULL, NULL, log_collection_upload_on_send_body, core);
belle_sip_body_handler_add_header((belle_sip_body_handler_t *)first_part_bh, belle_sip_header_create("Content-disposition", first_part_header));
belle_sip_free(first_part_header);
belle_sip_body_handler_add_header((belle_sip_body_handler_t *)first_part_bh, (belle_sip_header_t *)belle_sip_header_content_type_create(core->log_collection_upload_information->type, core->log_collection_upload_information->subtype));
/* Insert it in a multipart body handler which will manage the boundaries of multipart message */
bh = belle_sip_multipart_body_handler_new(log_collection_upload_on_progress, core, (belle_sip_body_handler_t *)first_part_bh);
ua = ms_strdup_printf("%s/%s", linphone_core_get_user_agent_name(), linphone_core_get_user_agent_version());
content_type = belle_sip_strdup_printf("multipart/form-data; boundary=%s", multipart_boundary);
uri = belle_generic_uri_parse(linphone_core_get_log_collection_upload_server_url(core));
req = belle_http_request_create("POST", uri, belle_sip_header_create("User-Agent", ua), belle_sip_header_create("Content-type", content_type), NULL);
ms_free(ua);
belle_sip_free(content_type);
belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(req), BELLE_SIP_BODY_HANDLER(bh));
cbs.process_response = process_response_from_post_file_log_collection;
cbs.process_io_error = process_io_error_upload_log_collection;
cbs.process_auth_requested = process_auth_requested_upload_log_collection;
l = belle_http_request_listener_create_from_callbacks(&cbs, core);
belle_http_provider_send_request(core->http_provider, req, l);
}
if (code == 200) { /* The file has been uploaded correctly, get the server reply */
xmlDocPtr xmlMessageBody;
xmlNodePtr cur;
xmlChar *file_url = NULL;
const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response);
xmlMessageBody = xmlParseDoc((const xmlChar *)body);
cur = xmlDocGetRootElement(xmlMessageBody);
if (cur != NULL) {
cur = cur->xmlChildrenNode;
while (cur != NULL) {
if (!xmlStrcmp(cur->name, (const xmlChar *)"file-info")) { /* we found a file info node, check it has a type="file" attribute */
xmlChar *typeAttribute = xmlGetProp(cur, (const xmlChar *)"type");
if (!xmlStrcmp(typeAttribute, (const xmlChar *)"file")) { /* this is the node we are looking for */
cur = cur->xmlChildrenNode; /* now loop on the content of the file-info node */
while (cur != NULL) {
if (!xmlStrcmp(cur->name, (const xmlChar *)"data")) {
file_url = xmlGetProp(cur, (const xmlChar *)"url");
}
cur=cur->next;
}
xmlFree(typeAttribute);
break;
}
xmlFree(typeAttribute);
}
cur = cur->next;
}
}
if (file_url != NULL) {
linphone_core_notify_log_collection_upload_state_changed(core, LinphoneCoreLogCollectionUploadStateDelivered, (const char *)file_url);
}
}
}
}
void linphone_core_upload_log_collection(LinphoneCore *core) {
if ((core->log_collection_upload_information == NULL) && (linphone_core_get_log_collection_upload_server_url(core) != NULL) && (liblinphone_log_collection_enabled == TRUE)) {
/* open a transaction with the server and send an empty request(RCS5.1 section 3.5.4.8.3.1) */
belle_http_request_listener_callbacks_t cbs = { 0 };
belle_http_request_listener_t *l;
belle_generic_uri_t *uri;
belle_http_request_t *req;
struct stat statbuf;
char *log_filename = ortp_strdup_printf("%s/%s", liblinphone_log_collection_path, "linphone1.log");
FILE *log_file = fopen(log_filename, "r");
fstat(fileno(log_file), &statbuf);
fclose(log_file);
ortp_free(log_filename);
core->log_collection_upload_information = (LinphoneContent *)malloc(sizeof(LinphoneContent));
memset(core->log_collection_upload_information, 0, sizeof(LinphoneContent));
core->log_collection_upload_information->type = "text";
core->log_collection_upload_information->subtype = "plain";
core->log_collection_upload_information->name = "linphone_log.txt";
core->log_collection_upload_information->size = statbuf.st_size;
uri = belle_generic_uri_parse(linphone_core_get_log_collection_upload_server_url(core));
req = belle_http_request_create("POST", uri, NULL, NULL, NULL);
cbs.process_response = process_response_from_post_file_log_collection;
cbs.process_io_error = process_io_error_upload_log_collection;
cbs.process_auth_requested = process_auth_requested_upload_log_collection;
l = belle_http_request_listener_create_from_callbacks(&cbs, core);
belle_http_provider_send_request(core->http_provider, req, l);
}
}
/**
* Enable logs in supplied FILE*.
*
@ -6632,6 +6894,12 @@ void linphone_core_notify_subscription_state_changed(LinphoneCore *lc, LinphoneE
void linphone_core_notify_publish_state_changed(LinphoneCore *lc, LinphoneEvent *lev, LinphonePublishState state) {
NOTIFY_IF_EXIST(publish_state_changed)(lc,lev,state);
}
void linphone_core_notify_log_collection_upload_state_changed(LinphoneCore *lc, LinphoneCoreLogCollectionUploadState state, const char *info) {
NOTIFY_IF_EXIST(log_collection_upload_state_changed)(lc, state, info);
}
void linphone_core_notify_log_collection_upload_progress_indication(LinphoneCore *lc, size_t progress) {
NOTIFY_IF_EXIST(log_collection_upload_progress_indication)(lc, progress);
}
void linphone_core_add_listener(LinphoneCore *lc, LinphoneCoreVTable *vtable) {
ms_message("Vtable [%p] registered on core [%p]",lc,vtable);
lc->vtables=ms_list_append(lc->vtables,vtable);

View file

@ -1464,6 +1464,14 @@ typedef enum _LinphoneGlobalState{
const char *linphone_global_state_to_string(LinphoneGlobalState gs);
/**
* LinphoneCoreLogCollectionUploadState is used to notify if log collection upload have been succesfully delivered or not.
*/
typedef enum _LinphoneCoreLogCollectionUploadState {
LinphoneCoreLogCollectionUploadStateInProgress, /**< Delivery in progress */
LinphoneCoreLogCollectionUploadStateDelivered, /**< Log collection upload successfully delivered and acknowledged by remote end point */
LinphoneCoreLogCollectionUploadStateNotDelivered, /**< Log collection upload was not delivered */
} LinphoneCoreLogCollectionUploadState;
/**
* Global state notification callback.
@ -1670,6 +1678,21 @@ typedef void (*LinphoneCoreConfiguringStatusCb)(LinphoneCore *lc, LinphoneConfig
*/
typedef void (*LinphoneCoreNetworkReachableCb)(LinphoneCore *lc, bool_t reachable);
/**
* Callback prototype for reporting log collection upload state change.
* @param[in] lc LinphoneCore object
* @param[in] state The state of the log collection upload
* @param[in] info Additional information: error message in case of error state, URL of uploaded file in case of success.
*/
typedef void (*LinphoneCoreLogCollectionUploadStateChangedCb)(LinphoneCore *lc, LinphoneCoreLogCollectionUploadState state, const char *info);
/**
* Callback prototype for reporting log collection upload progress indication.
* @param[in] lc LinphoneCore object
* @param[in] progress Percentage of the file size of the log collection already uploaded.
*/
typedef void (*LinphoneCoreLogCollectionUploadProgressIndicationCb)(LinphoneCore *lc, size_t progress);
/**
* This structure holds all callbacks that the application should implement.
* None is mandatory.
@ -1700,11 +1723,13 @@ typedef struct _LinphoneCoreVTable{
DisplayMessageCb display_warning;/**< @deprecated Callback to display a warning to the user */
DisplayUrlCb display_url; /**< @deprecated */
ShowInterfaceCb show; /**< @deprecated Notifies the application that it should show up*/
LinphoneCoreTextMessageReceivedCb text_received; /** @deprecated, use #message_received instead <br> A text message has been received */
LinphoneCoreFileTransferRecvCb file_transfer_recv; /** Callback to store file received attached to a #LinphoneChatMessage */
LinphoneCoreFileTransferSendCb file_transfer_send; /** Callback to collect file chunk to be sent for a #LinphoneChatMessage */
LinphoneCoreFileTransferProgressIndicationCb file_transfer_progress_indication; /**Callback to indicate file transfer progress*/
LinphoneCoreNetworkReachableCb network_reachable; /** Call back to report IP network status (I.E up/down)*/
LinphoneCoreTextMessageReceivedCb text_received; /**< @deprecated, use #message_received instead <br> A text message has been received */
LinphoneCoreFileTransferRecvCb file_transfer_recv; /**< Callback to store file received attached to a #LinphoneChatMessage */
LinphoneCoreFileTransferSendCb file_transfer_send; /**< Callback to collect file chunk to be sent for a #LinphoneChatMessage */
LinphoneCoreFileTransferProgressIndicationCb file_transfer_progress_indication; /**< Callback to indicate file transfer progress */
LinphoneCoreNetworkReachableCb network_reachable; /**< Callback to report IP network status (I.E up/down )*/
LinphoneCoreLogCollectionUploadStateChangedCb log_collection_upload_state_changed; /**< Callback to upload collected logs */
LinphoneCoreLogCollectionUploadProgressIndicationCb log_collection_upload_progress_indication; /**< Callback to indicate log collection upload progress */
} LinphoneCoreVTable;
/**
@ -1752,6 +1777,35 @@ typedef void * (*LinphoneCoreWaitingCallback)(LinphoneCore *lc, void *context, L
/* THE main API */
/**
* Enable the linphone core log collection to upload logs on a server.
* @ingroup misc
* @param[in] enable Boolean value telling whether to enable log collection or not.
*/
LINPHONE_PUBLIC void linphone_core_enable_log_collection(bool_t enable);
/**
* Set the path where the log files will be written for log collection.
* @ingroup misc
* @param[in] path The path where the log files will be written.
*/
LINPHONE_PUBLIC void linphone_core_set_log_collection_path(const char *path);
/**
* Set the url of the server where to upload the collected log files.
* @ingroup misc
* @param[in] core LinphoneCore object
* @param[in] server_url The url of the server where to upload the collected log files.
*/
LINPHONE_PUBLIC void linphone_core_set_log_collection_upload_server_url(LinphoneCore *core, const char *server_url);
/**
* Upload the log collection to the configured server url.
* @ingroup misc
* @param[in] core LinphoneCore object
*/
LINPHONE_PUBLIC void linphone_core_upload_log_collection(LinphoneCore *core);
/**
* Define a log handler.
*

View file

@ -767,6 +767,7 @@ struct _LinphoneCore
MSList *tones;
LinphoneReason chat_deny_code;
const char **supported_formats;
LinphoneContent *log_collection_upload_information;
};
@ -1018,6 +1019,8 @@ void linphone_core_notify_network_reachable(LinphoneCore *lc, bool_t reachable);
void linphone_core_notify_notify_received(LinphoneCore *lc, LinphoneEvent *lev, const char *notified_event, const LinphoneContent *body);
void linphone_core_notify_subscription_state_changed(LinphoneCore *lc, LinphoneEvent *lev, LinphoneSubscriptionState state);
void linphone_core_notify_publish_state_changed(LinphoneCore *lc, LinphoneEvent *lev, LinphonePublishState state);
void linphone_core_notify_log_collection_upload_state_changed(LinphoneCore *lc, LinphoneCoreLogCollectionUploadState state, const char *info);
void linphone_core_notify_log_collection_upload_progress_indication(LinphoneCore *lc, size_t progress);
void set_mic_gain_db(AudioStream *st, float gain);
void set_playback_gain_db(AudioStream *st, float gain);