/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

#include "log.h"

#include "zbxmutexs.h"
#include "zbxthreads.h"
#include "cfg.h"
#include "zbxstr.h"
#include "zbxtime.h"
#ifdef _WINDOWS
#	include "messages.h"
#	include "zbxwinservice.h"
#	include "zbxsysinfo.h"
static HANDLE		system_log_handle = INVALID_HANDLE_VALUE;
#endif

#define LOG_COMPONENT_NAME_LEN	64

static char			log_filename[MAX_STRING_LEN];
static int			log_type = LOG_TYPE_UNDEFINED;
static zbx_mutex_t		log_access = ZBX_MUTEX_NULL;

static int			zbx_log_level = LOG_LEVEL_WARNING;
ZBX_THREAD_LOCAL int		*zbx_plog_level = &zbx_log_level;

#define LOG_LEVEL_DEC_FAIL	-2
#define LOG_LEVEL_DEC_SUCCEED	-1
#define LOG_LEVEL_UNCHANGED	0
#define LOG_LEVEL_INC_SUCCEED	1
#define LOG_LEVEL_INC_FAIL	2

static ZBX_THREAD_LOCAL int	zbx_log_level_change = LOG_LEVEL_UNCHANGED;

static ZBX_THREAD_LOCAL char	log_component_name[LOG_COMPONENT_NAME_LEN + 1];

static int			config_log_file_size = -1;	/* max log file size in MB */

static int	get_config_log_file_size(void)
{
	if (-1 != config_log_file_size)
		return config_log_file_size;

	THIS_SHOULD_NEVER_HAPPEN;
	exit(EXIT_FAILURE);
}

#ifdef _WINDOWS
#	define LOCK_LOG		zbx_mutex_lock(log_access)
#	define UNLOCK_LOG	zbx_mutex_unlock(log_access)
#else
#	define LOCK_LOG		lock_log()
#	define UNLOCK_LOG	unlock_log()
#endif

#ifdef _WINDOWS
#	define STDIN_FILENO	_fileno(stdin)
#	define STDOUT_FILENO	_fileno(stdout)
#	define STDERR_FILENO	_fileno(stderr)

#	define ZBX_DEV_NULL	"NUL"

#	define dup2(fd1, fd2)	_dup2(fd1, fd2)
#else
#	define ZBX_DEV_NULL	"/dev/null"
#endif

#ifndef _WINDOWS

static const char	*zabbix_get_log_level_ref_string(int loglevel)
{
	switch (loglevel)
	{
		case LOG_LEVEL_EMPTY:
			return "0 (none)";
		case LOG_LEVEL_CRIT:
			return "1 (critical)";
		case LOG_LEVEL_ERR:
			return "2 (error)";
		case LOG_LEVEL_WARNING:
			return "3 (warning)";
		case LOG_LEVEL_DEBUG:
			return "4 (debug)";
		case LOG_LEVEL_TRACE:
			return "5 (trace)";
	}

	THIS_SHOULD_NEVER_HAPPEN;
	exit(EXIT_FAILURE);
}

const char	*zabbix_get_log_level_string(void)
{
	return zabbix_get_log_level_ref_string(*zbx_plog_level);
}

void	zabbix_increase_log_level(void)
{
	if (LOG_LEVEL_TRACE == *zbx_plog_level)
	{
		zbx_log_level_change = LOG_LEVEL_INC_FAIL;
		return;
	}

	zbx_log_level_change = LOG_LEVEL_INC_SUCCEED;

	*zbx_plog_level = *zbx_plog_level + 1;

	return;
}

void	zabbix_decrease_log_level(void)
{
	if (LOG_LEVEL_EMPTY == *zbx_plog_level)
	{
		zbx_log_level_change = LOG_LEVEL_DEC_FAIL;
		return;
	}

	zbx_log_level_change = LOG_LEVEL_DEC_SUCCEED;

	*zbx_plog_level = *zbx_plog_level - 1;

	return;
}

/******************************************************************************
 *                                                                            *
 * Purpose: log last loglevel change result                                   *
 *                                                                            *
 * Comments: With consequent fast changes only the last attempt result would  *
 *           be logged.                                                       *
 *                                                                            *
 ******************************************************************************/
void	 zabbix_report_log_level_change(void)
{
	int	change;

	if (0 == zbx_log_level_change)
		return;

	/* reset log level change history to avoid recursion */
	change = zbx_log_level_change;
	zbx_log_level_change = LOG_LEVEL_UNCHANGED;

	switch (change)
	{
		case LOG_LEVEL_DEC_FAIL:
			zabbix_log(LOG_LEVEL_INFORMATION, "cannot decrease log level:"
					" minimum level has been already set");
			break;
		case LOG_LEVEL_DEC_SUCCEED:
			zabbix_log(LOG_LEVEL_INFORMATION, "log level has been decreased to %s",
					zabbix_get_log_level_string());
			break;
		case LOG_LEVEL_INC_SUCCEED:
			zabbix_log(LOG_LEVEL_INFORMATION, "log level has been increased to %s",
					zabbix_get_log_level_string());
			break;
		case LOG_LEVEL_INC_FAIL:
			zabbix_log(LOG_LEVEL_INFORMATION, "cannot increase log level:"
					" maximum level has been already set");
			break;
	}
}

void	zbx_set_log_component(const char *name, zbx_log_component_t *component)
{
	int	log_level = *zbx_plog_level;

	zbx_snprintf(log_component_name, sizeof(log_component_name), "[%s] ", name);

	zbx_plog_level = &component->level;
	component->level = log_level;
	component->name = log_component_name;
}

/******************************************************************************
 *                                                                            *
 * Purpose: change log level of the specified component                       *
 *                                                                            *
 * Comments: This function is used to change log level managed threads.       *
 *                                                                            *
 ******************************************************************************/
void	zbx_change_component_log_level(zbx_log_component_t *component, int direction)
{
	if (0 > direction)
	{
		if (LOG_LEVEL_EMPTY == component->level)
		{
			zabbix_log(LOG_LEVEL_INFORMATION, "%scannot decrease log level:"
					" minimum level has been already set", component->name);
		}
		else
		{
			component->level += direction;
			zabbix_log(LOG_LEVEL_INFORMATION, "%slog level has been decreased to %s",
					component->name, zabbix_get_log_level_ref_string(component->level));
		}
	}
	else
	{
		if (LOG_LEVEL_TRACE == component->level)
		{
			zabbix_log(LOG_LEVEL_INFORMATION, "%scannot increase log level:"
					" maximum level has been already set", component->name);
		}
		else
		{
			component->level += direction;
			zabbix_log(LOG_LEVEL_INFORMATION, "%slog level has been increased to %s",
					component->name, zabbix_get_log_level_ref_string(component->level));
		}
	}
}
#endif

int	zbx_redirect_stdio(const char *filename)
{
	const char	default_file[] = ZBX_DEV_NULL;
	int		open_flags = O_WRONLY, fd;

	if (NULL != filename && '\0' != *filename)
		open_flags |= O_CREAT | O_APPEND;
	else
		filename = default_file;

	if (-1 == (fd = open(filename, open_flags, 0666)))
	{
		zbx_error("cannot open \"%s\": %s", filename, zbx_strerror(errno));
		return FAIL;
	}

	fflush(stdout);
	if (-1 == dup2(fd, STDOUT_FILENO))
		zbx_error("cannot redirect stdout to \"%s\": %s", filename, zbx_strerror(errno));

	fflush(stderr);
	if (-1 == dup2(fd, STDERR_FILENO))
		zbx_error("cannot redirect stderr to \"%s\": %s", filename, zbx_strerror(errno));

	close(fd);

	if (-1 == (fd = open(default_file, O_RDONLY)))
	{
		zbx_error("cannot open \"%s\": %s", default_file, zbx_strerror(errno));
		return FAIL;
	}

	if (-1 == dup2(fd, STDIN_FILENO))
		zbx_error("cannot redirect stdin to \"%s\": %s", default_file, zbx_strerror(errno));

	close(fd);

	return SUCCEED;
}

static void	rotate_log(const char *filename)
{
	zbx_stat_t		buf;
	zbx_uint64_t		new_size;
	static zbx_uint64_t	old_size = ZBX_MAX_UINT64;	/* redirect stdout and stderr */
#if !defined(_WINDOWS)
	static zbx_uint64_t	st_ino, st_dev;
#endif

	if (0 != zbx_stat(filename, &buf))
	{
		zbx_redirect_stdio(filename);
		return;
	}

	new_size = buf.st_size;

	if (0 != get_config_log_file_size() && (zbx_uint64_t)get_config_log_file_size() * ZBX_MEBIBYTE < new_size)
	{
		char	filename_old[MAX_STRING_LEN];

		zbx_strscpy(filename_old, filename);
		zbx_strlcat(filename_old, ".old", MAX_STRING_LEN);
		remove(filename_old);
#ifdef _WINDOWS
		zbx_redirect_stdio(NULL);
#endif
		if (0 != rename(filename, filename_old))
		{
			FILE	*log_file = NULL;

			if (NULL != (log_file = fopen(filename, "w")))
			{
				long		milliseconds;
				struct tm	tm;

				zbx_get_time(&tm, &milliseconds, NULL);

				fprintf(log_file, "%6li:%.4d%.2d%.2d:%.2d%.2d%.2d.%03ld"
						" cannot rename log file \"%s\" to \"%s\": %s\n",
						zbx_get_thread_id(),
						tm.tm_year + 1900,
						tm.tm_mon + 1,
						tm.tm_mday,
						tm.tm_hour,
						tm.tm_min,
						tm.tm_sec,
						milliseconds,
						filename,
						filename_old,
						zbx_strerror(errno));

				fprintf(log_file, "%6li:%.4d%.2d%.2d:%.2d%.2d%.2d.%03ld"
						" Logfile \"%s\" size reached configured limit"
						" LogFileSize but moving it to \"%s\" failed. The logfile"
						" was truncated.\n",
						zbx_get_thread_id(),
						tm.tm_year + 1900,
						tm.tm_mon + 1,
						tm.tm_mday,
						tm.tm_hour,
						tm.tm_min,
						tm.tm_sec,
						milliseconds,
						filename,
						filename_old);

				zbx_fclose(log_file);

				new_size = 0;
			}
		}
		else
			new_size = 0;
	}

	if (old_size > new_size)
		zbx_redirect_stdio(filename);
#if !defined(_WINDOWS)
	else if (st_ino != buf.st_ino || st_dev != buf.st_dev)
	{
		st_ino = buf.st_ino;
		st_dev = buf.st_dev;
		zbx_redirect_stdio(filename);
	}
#endif

	old_size = new_size;
}

#ifndef _WINDOWS
static ZBX_THREAD_LOCAL sigset_t	orig_mask;

static void	lock_log(void)
{
	sigset_t	mask;

	/* block signals to prevent deadlock on log file mutex when signal handler attempts to lock log */
	sigemptyset(&mask);
	sigaddset(&mask, SIGUSR1);
	sigaddset(&mask, SIGUSR2);
	sigaddset(&mask, SIGTERM);
	sigaddset(&mask, SIGINT);
	sigaddset(&mask, SIGQUIT);
	sigaddset(&mask, SIGHUP);

	if (0 > zbx_sigmask(SIG_BLOCK, &mask, &orig_mask))
		zbx_error("cannot set signal mask to block the user signal");

	zbx_mutex_lock(log_access);
}

static void	unlock_log(void)
{
	zbx_mutex_unlock(log_access);

	if (0 > zbx_sigmask(SIG_SETMASK, &orig_mask, NULL))
		zbx_error("cannot restore signal mask");
}
#else
static void	lock_log(void)
{
#ifdef ZABBIX_AGENT
	if (0 == (ZBX_MUTEX_LOGGING_DENIED & zbx_get_thread_global_mutex_flag()))
#endif
		LOCK_LOG;
}

static void	unlock_log(void)
{
#ifdef ZABBIX_AGENT
	if (0 == (ZBX_MUTEX_LOGGING_DENIED & zbx_get_thread_global_mutex_flag()))
#endif
		UNLOCK_LOG;
}
#endif

void	zbx_handle_log(void)
{
#ifndef _WINDOWS
	zabbix_report_log_level_change();
#endif
	if (LOG_TYPE_FILE != log_type)
		return;

	LOCK_LOG;

	rotate_log(log_filename);

	UNLOCK_LOG;
}

int	zabbix_open_log(const zbx_config_log_t *log_file_cfg, int level, char **error)
{
	const char	*filename = log_file_cfg->log_file_name;
	int		type = log_file_cfg->log_type;

	log_type = type;
	*zbx_plog_level = level;
	config_log_file_size = log_file_cfg->log_file_size;

	if (LOG_TYPE_SYSTEM == type)
	{
#ifdef _WINDOWS
		wchar_t	*wevent_source;

		wevent_source = zbx_utf8_to_unicode(ZABBIX_EVENT_SOURCE);
		system_log_handle = RegisterEventSource(NULL, wevent_source);
		zbx_free(wevent_source);
#else
		openlog(syslog_app_name, LOG_PID, LOG_DAEMON);
#endif
	}
	else if (LOG_TYPE_FILE == type)
	{
		FILE	*log_file = NULL;

		if (MAX_STRING_LEN <= strlen(filename))
		{
			*error = zbx_strdup(*error, "too long path for logfile");
			return FAIL;
		}

		if (SUCCEED != zbx_mutex_create(&log_access, ZBX_MUTEX_LOG, error))
			return FAIL;

		if (NULL == (log_file = fopen(filename, "a+")))
		{
			*error = zbx_dsprintf(*error, "unable to open log file [%s]: %s", filename,
					zbx_strerror(errno));
			return FAIL;
		}

		zbx_strscpy(log_filename, filename);
		zbx_fclose(log_file);
	}
	else if (LOG_TYPE_CONSOLE == type || LOG_TYPE_UNDEFINED == type)
	{
		if (SUCCEED != zbx_mutex_create(&log_access, ZBX_MUTEX_LOG, error))
		{
			*error = zbx_strdup(*error, "unable to create mutex for standard output");
			return FAIL;
		}

		fflush(stderr);
		if (-1 == dup2(STDOUT_FILENO, STDERR_FILENO))
			zbx_error("cannot redirect stderr to stdout: %s", zbx_strerror(errno));
	}
	else
	{
		*error = zbx_strdup(*error, "unknown log type");
		return FAIL;
	}

	return SUCCEED;
}

void	zabbix_close_log(void)
{
	if (LOG_TYPE_SYSTEM == log_type)
	{
#ifdef _WINDOWS
		if (NULL != system_log_handle)
			DeregisterEventSource(system_log_handle);
#else
		closelog();
#endif
	}
	else if (LOG_TYPE_FILE == log_type || LOG_TYPE_CONSOLE == log_type || LOG_TYPE_UNDEFINED == log_type)
	{
		zbx_mutex_destroy(&log_access);
	}

	log_type = LOG_TYPE_UNDEFINED;
}

void	__zbx_zabbix_log(int level, const char *fmt, ...)
{
	char		message[MAX_BUFFER_LEN];
	va_list		args;
#ifdef _WINDOWS
	WORD		wType;
	wchar_t		thread_id[20], *strings[2];
#else
	zabbix_report_log_level_change();
#endif

#ifndef ZBX_ZABBIX_LOG_CHECK
	if (SUCCEED != ZBX_CHECK_LOG_LEVEL(level))
		return;
#endif

	if (LOG_TYPE_FILE == log_type)
	{
		FILE	*log_file;

		LOCK_LOG;

		if (0 != get_config_log_file_size())
			rotate_log(log_filename);

		if (NULL != (log_file = fopen(log_filename, "a+")))
		{
			long		milliseconds;
			struct tm	tm;

			zbx_get_time(&tm, &milliseconds, NULL);

			fprintf(log_file,
					"%6li:%.4d%.2d%.2d:%.2d%.2d%.2d.%03ld %s",
					zbx_get_thread_id(),
					tm.tm_year + 1900,
					tm.tm_mon + 1,
					tm.tm_mday,
					tm.tm_hour,
					tm.tm_min,
					tm.tm_sec,
					milliseconds,
					log_component_name
					);

			va_start(args, fmt);
			vfprintf(log_file, fmt, args);
			va_end(args);

			fprintf(log_file, "\n");

			zbx_fclose(log_file);
		}
		else
		{
			zbx_error("failed to open log file: %s", zbx_strerror(errno));

			va_start(args, fmt);
			zbx_vsnprintf(message, sizeof(message), fmt, args);
			va_end(args);

			zbx_error("failed to write [%s] into log file", message);
		}

		UNLOCK_LOG;

		return;
	}

	if (LOG_TYPE_CONSOLE == log_type)
	{
		long		milliseconds;
		struct tm	tm;

		LOCK_LOG;

		zbx_get_time(&tm, &milliseconds, NULL);

		fprintf(stdout,
				"%6li:%.4d%.2d%.2d:%.2d%.2d%.2d.%03ld %s",
				zbx_get_thread_id(),
				tm.tm_year + 1900,
				tm.tm_mon + 1,
				tm.tm_mday,
				tm.tm_hour,
				tm.tm_min,
				tm.tm_sec,
				milliseconds,
				log_component_name
				);

		va_start(args, fmt);
		vfprintf(stdout, fmt, args);
		va_end(args);

		fprintf(stdout, "\n");

		fflush(stdout);

		UNLOCK_LOG;

		return;
	}

	va_start(args, fmt);
	zbx_vsnprintf(message, sizeof(message), fmt, args);
	va_end(args);

	if (LOG_TYPE_SYSTEM == log_type)
	{
#ifdef _WINDOWS
		switch (level)
		{
			case LOG_LEVEL_CRIT:
			case LOG_LEVEL_ERR:
				wType = EVENTLOG_ERROR_TYPE;
				break;
			case LOG_LEVEL_WARNING:
				wType = EVENTLOG_WARNING_TYPE;
				break;
			default:
				wType = EVENTLOG_INFORMATION_TYPE;
				break;
		}

		StringCchPrintf(thread_id, ARRSIZE(thread_id), TEXT("[%li]: "), zbx_get_thread_id());
		strings[0] = thread_id;
		strings[1] = zbx_utf8_to_unicode(message);

		ReportEvent(
			system_log_handle,
			wType,
			0,
			MSG_ZABBIX_MESSAGE,
			NULL,
			sizeof(strings) / sizeof(*strings),
			0,
			strings,
			NULL);

		zbx_free(strings[1]);

#else	/* not _WINDOWS */

		/* for nice printing into syslog */
		switch (level)
		{
			case LOG_LEVEL_CRIT:
				syslog(LOG_CRIT, "%s%s", log_component_name, message);
				break;
			case LOG_LEVEL_ERR:
				syslog(LOG_ERR, "%s%s", log_component_name, message);
				break;
			case LOG_LEVEL_WARNING:
				syslog(LOG_WARNING, "%s%s", log_component_name, message);
				break;
			case LOG_LEVEL_DEBUG:
			case LOG_LEVEL_TRACE:
				syslog(LOG_DEBUG, "%s%s", log_component_name, message);
				break;
			case LOG_LEVEL_INFORMATION:
				syslog(LOG_INFO, "%s%s", log_component_name, message);
				break;
			default:
				/* LOG_LEVEL_EMPTY - print nothing */
				break;
		}

#endif	/* _WINDOWS */
	}	/* LOG_TYPE_SYSLOG */
	else	/* LOG_TYPE_UNDEFINED == log_type */
	{
		LOCK_LOG;

		switch (level)
		{
			case LOG_LEVEL_CRIT:
				zbx_error("ERROR: %s%s", log_component_name, message);
				break;
			case LOG_LEVEL_ERR:
				zbx_error("Error: %s%s", log_component_name, message);
				break;
			case LOG_LEVEL_WARNING:
				zbx_error("Warning: %s%s", log_component_name, message);
				break;
			case LOG_LEVEL_DEBUG:
				zbx_error("DEBUG: %s%s", log_component_name, message);
				break;
			case LOG_LEVEL_TRACE:
				zbx_error("TRACE: %s%s", log_component_name, message);
				break;
			default:
				zbx_error("%s%s", log_component_name, message);
				break;
		}

		UNLOCK_LOG;
	}
}

int	zbx_get_log_type(const char *logtype)
{
	const char	*logtypes[] = {ZBX_OPTION_LOGTYPE_SYSTEM, ZBX_OPTION_LOGTYPE_FILE, ZBX_OPTION_LOGTYPE_CONSOLE};
	int		i;

	for (i = 0; i < (int)ARRSIZE(logtypes); i++)
	{
		if (0 == strcmp(logtype, logtypes[i]))
			return i + 1;
	}

	return LOG_TYPE_UNDEFINED;
}

int	zbx_validate_log_parameters(ZBX_TASK_EX *task, const zbx_config_log_t *log_file_cfg)
{
	if (LOG_TYPE_UNDEFINED == log_file_cfg->log_type)
	{
		zabbix_log(LOG_LEVEL_CRIT, "invalid \"LogType\" configuration parameter: '%s'",
				log_file_cfg->log_type_str);
		return FAIL;
	}

	if (LOG_TYPE_CONSOLE == log_file_cfg->log_type && 0 == (task->flags & ZBX_TASK_FLAG_FOREGROUND) &&
			ZBX_TASK_START == task->task)
	{
		zabbix_log(LOG_LEVEL_CRIT, "\"LogType\" \"console\" parameter can only be used with the"
				" -f (--foreground) command line option");
		return FAIL;
	}

	if (LOG_TYPE_FILE == log_file_cfg->log_type && (NULL == log_file_cfg->log_file_name || '\0' ==
			*log_file_cfg->log_file_name))
	{
		zabbix_log(LOG_LEVEL_CRIT, "\"LogType\" \"file\" parameter requires \"LogFile\" parameter to be set");
		return FAIL;
	}

	return SUCCEED;
}

char	*strerror_from_system(unsigned long error)
{
#ifdef _WINDOWS
	size_t		offset = 0;
	wchar_t		wide_string[ZBX_MESSAGE_BUF_SIZE];
	/* !!! Attention: static !!! Not thread-safe for Win32 */
	static char	utf8_string[ZBX_MESSAGE_BUF_SIZE];

	offset += zbx_snprintf(utf8_string, sizeof(utf8_string), "[0x%08lX] ", error);

	/* we don't know the inserts so we pass NULL and enable appropriate flag */
	if (0 == FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error,
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), wide_string, ZBX_MESSAGE_BUF_SIZE, NULL))
	{
		zbx_snprintf(utf8_string + offset, sizeof(utf8_string) - offset,
				"unable to find message text [0x%08lX]", GetLastError());

		return utf8_string;
	}

	zbx_unicode_to_utf8_static(wide_string, utf8_string + offset, (int)(sizeof(utf8_string) - offset));

	zbx_rtrim(utf8_string, "\r\n ");

	return utf8_string;
#else	/* not _WINDOWS */
	ZBX_UNUSED(error);

	return zbx_strerror(errno);
#endif	/* _WINDOWS */
}

#ifdef _WINDOWS
char	*strerror_from_module(unsigned long error, const wchar_t *module)
{
	size_t		offset = 0;
	wchar_t		wide_string[ZBX_MESSAGE_BUF_SIZE];
	HMODULE		hmodule;
	/* !!! Attention: static !!! not thread-safe for Win32 */
	static char	utf8_string[ZBX_MESSAGE_BUF_SIZE];

	*utf8_string = '\0';
	hmodule = GetModuleHandle(module);

	offset += zbx_snprintf(utf8_string, sizeof(utf8_string), "[0x%08lX] ", error);

	/* we don't know the inserts so we pass NULL and enable appropriate flag */
	if (0 == FormatMessage(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, hmodule, error,
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), wide_string, sizeof(wide_string), NULL))
	{
		zbx_snprintf(utf8_string + offset, sizeof(utf8_string) - offset,
				"unable to find message text: %s", strerror_from_system(GetLastError()));

		return utf8_string;
	}

	zbx_unicode_to_utf8_static(wide_string, utf8_string + offset, (int)(sizeof(utf8_string) - offset));

	zbx_rtrim(utf8_string, "\r\n ");

	return utf8_string;
}
#endif	/* _WINDOWS */

/******************************************************************************
 *                                                                            *
 * Purpose: log the message optionally appending to a string buffer           *
 *                                                                            *
 * Parameters: level      - [IN] the log level                                *
 *             out        - [OUT] the output buffer (optional)                *
 *             out_alloc  - [OUT] the output buffer size                      *
 *             out_offset - [OUT] the output buffer offset                    *
 *             format     - [IN] the format string                            *
 *                                                                            *
 * Return value: SUCCEED - the socket was successfully opened                 *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
void	zbx_strlog_alloc(int level, char **out, size_t *out_alloc, size_t *out_offset, const char *format, ...)
{
	va_list	args;
	size_t	len;
	char	*buf;

	if (SUCCEED != ZBX_CHECK_LOG_LEVEL(level) && NULL == out)
		return;

	va_start(args, format);
	len = (size_t)vsnprintf(NULL, 0, format, args) + 2;
	va_end(args);

	buf = (char *)zbx_malloc(NULL, len);

	va_start(args, format);
	len = (size_t)vsnprintf(buf, len, format, args);
	va_end(args);

	if (SUCCEED == ZBX_CHECK_LOG_LEVEL(level))
		zabbix_log(level, "%s", buf);

	if (NULL != out)
	{
		buf[0] = (char)toupper((unsigned char)buf[0]);
		buf[len++] = '\n';
		buf[len] = '\0';

		zbx_strcpy_alloc(out, out_alloc, out_offset, buf);
	}

	zbx_free(buf);
}