1 /*
2  * Logging for unix service.
3  *
4  * Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ
5  *
6  * Permission to use, copy, modify, and/or distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <usual/logging.h>
20 
21 #include <sys/stat.h>
22 
23 #include <usual/ctype.h>
24 #include <usual/string.h>
25 #include <usual/time.h>
26 #include <usual/err.h>
27 
28 #ifdef HAVE_SYSLOG_H
29 #include <syslog.h>
30 #endif
31 
32 #ifdef USE_SYSTEMD
33 #define SD_JOURNAL_SUPPRESS_LOCATION
34 #include <systemd/sd-journal.h>
35 #endif
36 
37 #ifdef WIN32
38 #define LOG_EMERG       0
39 #define LOG_ALERT       1
40 #define LOG_CRIT        2
41 #define LOG_ERR         3
42 #define LOG_WARNING     4
43 #define LOG_NOTICE      5
44 #define LOG_INFO        6
45 #define LOG_DEBUG       7
46 
47 #define LOG_PID 0
48 #define LOG_DAEMON 0
49 
openlog(const char * ident,int opt,int fac)50 static inline void openlog(const char *ident, int opt, int fac) {}
51 #define syslog win32_eventlog
52 #define closelog()
53 static void win32_eventlog(int level, const char *fmt, ...) _PRINTF(2, 3);
54 #endif
55 
56 int cf_quiet = 0;
57 int cf_verbose = 0;
58 const char *cf_logfile = NULL;
59 
60 int cf_syslog = 0;
61 const char *cf_syslog_ident = NULL;
62 const char *cf_syslog_facility = NULL;
63 
64 enum LogLevel cf_syslog_level = LG_INFO;
65 enum LogLevel cf_logfile_level = LG_NOISE;
66 enum LogLevel cf_stderr_level = LG_NOISE;
67 
68 /* optional function to fill prefix */
69 logging_prefix_fn_t logging_prefix_cb;
70 
71 static FILE *log_file = NULL;
72 static bool syslog_started = false;
73 
74 struct LevelInfo {
75 	const char *tag;
76 	int syslog_prio;
77 };
78 
79 static const struct LevelInfo log_level_list[] = {
80 	{ "FATAL", LOG_CRIT },	/* LG_FATAL */
81 	{ "ERROR", LOG_ERR },	/* LG_ERROR */
82 	{ "WARNING", LOG_WARNING },/* LG_WARNING */
83 	{ "LOG", LOG_INFO },	/* LG_STATS*/
84 	{ "LOG", LOG_INFO },	/* LG_INFO */
85 	{ "DEBUG", LOG_DEBUG },	/* LG_DEBUG */
86 	{ "NOISE", LOG_DEBUG },	/* LG_NOISE */
87 };
88 
89 struct FacName { const char *name; int code; };
90 static const struct FacName facility_names [] = {
91 #ifndef WIN32
92 	{ "auth",	LOG_AUTH },
93 #ifdef LOG_AUTHPRIV
94 	{ "authpriv",	LOG_AUTHPRIV },
95 #endif
96 	{ "daemon",	LOG_DAEMON },
97 	{ "user",	LOG_USER },
98 	{ "local0",	LOG_LOCAL0 },
99 	{ "local1",	LOG_LOCAL1 },
100 	{ "local2",	LOG_LOCAL2 },
101 	{ "local3",	LOG_LOCAL3 },
102 	{ "local4",	LOG_LOCAL4 },
103 	{ "local5",	LOG_LOCAL5 },
104 	{ "local6",	LOG_LOCAL6 },
105 	{ "local7",	LOG_LOCAL7 },
106 #endif
107 	{ NULL },
108 };
109 
reset_logging(void)110 void reset_logging(void)
111 {
112 	if (log_file) {
113 		fclose(log_file);
114 		log_file = NULL;
115 	}
116 	if (syslog_started) {
117 		closelog();
118 		syslog_started = 0;
119 	}
120 }
121 
122 
start_syslog(void)123 static void start_syslog(void)
124 {
125 	const struct FacName *f;
126 	int fac = LOG_DAEMON;
127 	const char *ident = cf_syslog_ident;
128 
129 	if (!cf_syslog)
130 		return;
131 
132 	if (cf_syslog_facility) {
133 		for (f = facility_names; f->name; f++) {
134 			if (strcmp(f->name, cf_syslog_facility) == 0) {
135 				fac = f->code;
136 				break;
137 			}
138 		}
139 	}
140 
141 	if (!ident) {
142 		ident = getprogname();
143 		if (!ident)
144 			ident = "unnamed";
145 	}
146 
147 	openlog(ident, LOG_PID, fac);
148 	syslog_started = 1;
149 }
150 
151 
log_generic(enum LogLevel level,void * ctx,const char * fmt,...)152 void log_generic(enum LogLevel level, void *ctx, const char *fmt, ...)
153 {
154 	char buf[2048], buf2[2048];
155 	char ebuf[256];
156 	char timebuf[64];
157 	const struct LevelInfo *lev = &log_level_list[level];
158 	unsigned pid = getpid();
159 	va_list ap;
160 	int pfxlen = 0;
161 	int old_errno = errno;
162 	char *msg = buf;
163 
164 	if (logging_prefix_cb) {
165 		pfxlen = logging_prefix_cb(level, ctx, buf, sizeof(buf));
166 		if (pfxlen < 0)
167 			goto done;
168 		if (pfxlen >= (int)sizeof(buf))
169 			pfxlen = sizeof(buf) - 1;
170 	}
171 	va_start(ap, fmt);
172 	vsnprintf(buf + pfxlen, sizeof(buf) - pfxlen, fmt, ap);
173 	va_end(ap);
174 
175 	/* replace '\n' in message with '\n\t', strip trailing whitespace */
176 	if (strchr(msg, '\n')) {
177 		char *dst = buf2;
178 		for (; *msg && dst - buf2 < (int)sizeof(buf2) - 2; msg++) {
179 			*dst++ = *msg;
180 			if (*msg == '\n')
181 				*dst++ = '\t';
182 		}
183 		while (dst > buf2 && isspace(dst[-1]))
184 			dst--;
185 		*dst = 0;
186 		msg = buf2;
187 	}
188 
189 	format_time_ms(0, timebuf, sizeof(timebuf));
190 
191 	if (!log_file && cf_logfile && cf_logfile[0]) {
192 		log_file = fopen(cf_logfile, "a");
193 		if (log_file) {
194 			/* Got the file, disable buffering */
195 			setvbuf(log_file, NULL, _IONBF, 0);
196 		} else {
197 			/* Unable to open, complain and fail */
198 			fprintf(stderr, "%s %u %s Cannot open logfile: '%s': %s\n",
199 				timebuf, pid, log_level_list[0].tag,
200 				cf_logfile,
201 				strerror_r(errno, ebuf, sizeof(ebuf)));
202 			exit(1);
203 		}
204 	}
205 
206 	if (!cf_quiet && level <= cf_stderr_level) {
207 #ifdef USE_SYSTEMD
208 		static bool journal_stream_checked = false;
209 		static bool use_systemd_journal = false;
210 
211 		if (!journal_stream_checked) {
212 			if (getenv("JOURNAL_STREAM")) {
213 				long long unsigned int f1, f2;
214 				if (sscanf(getenv("JOURNAL_STREAM"), "%llu:%llu", &f1, &f2) == 2) {
215 					struct stat st;
216 					dev_t js_dev = f1;
217 					ino_t js_ino = f2;
218 					if (fstat(fileno(stderr), &st) >= 0)
219 						if (js_dev == st.st_dev && js_ino == st.st_ino)
220 							use_systemd_journal = true;
221 				}
222 
223 			}
224 			journal_stream_checked = true;
225 		}
226 		if (use_systemd_journal)
227 			sd_journal_print(lev->syslog_prio, "%s", msg);
228 		else
229 #endif
230 			fprintf(stderr, "%s [%u] %s %s\n", timebuf, pid, lev->tag, msg);
231 	}
232 
233 	if (log_file && level <= cf_logfile_level)
234 		fprintf(log_file, "%s [%u] %s %s\n", timebuf, pid, lev->tag, msg);
235 
236 	if (cf_syslog && level <= cf_syslog_level) {
237 		if (!syslog_started)
238 			start_syslog();
239 		syslog(lev->syslog_prio, "%s", msg);
240 	}
241 done:
242 	if (old_errno != errno)
243 		errno = old_errno;
244 }
245 
246 
log_fatal(const char * file,int line,const char * func,bool show_perror,void * ctx,const char * fmt,...)247 void log_fatal(const char *file, int line, const char *func, bool show_perror, void *ctx, const char *fmt, ...)
248 {
249 	char buf[2048], ebuf[256];
250 	const char *estr = NULL;
251 	int old_errno = 0;
252 	va_list ap;
253 
254 	if (show_perror) {
255 		old_errno = errno;
256 		estr = strerror_r(errno, ebuf, sizeof(ebuf));
257 	}
258 
259 	va_start(ap, fmt);
260 	vsnprintf(buf, sizeof(buf), fmt, ap);
261 	va_end(ap);
262 
263 	if (show_perror) {
264 		log_generic(LG_FATAL, ctx, "@%s:%d in function %s(): %s: %s [%d]",
265 			     file, line, func, buf, estr, old_errno);
266 	} else {
267 		log_generic(LG_FATAL, ctx, "@%s:%d in function %s(): %s",
268 			     file, line, func, buf);
269 	}
270 }
271 
272 #ifdef WIN32
273 
win32_eventlog(int level,const char * fmt,...)274 static void win32_eventlog(int level, const char *fmt, ...)
275 {
276 	static HANDLE evtHandle = INVALID_HANDLE_VALUE;
277 	int elevel;
278 	char buf[1024];
279 	const char *strlist[1] = { buf };
280 	va_list ap;
281 
282 	va_start(ap, fmt);
283 	vsnprintf(buf, sizeof(buf), fmt, ap);
284 	va_end(ap);
285 
286 	switch (level) {
287 	case LOG_CRIT:
288 	case LOG_ERR:
289 		elevel = EVENTLOG_ERROR_TYPE;
290 		break;
291 	case LOG_WARNING:
292 		elevel = EVENTLOG_WARNING_TYPE;
293 		break;
294 	default:
295 		elevel = EVENTLOG_INFORMATION_TYPE;
296 	}
297 
298 	if (evtHandle == INVALID_HANDLE_VALUE) {
299 		evtHandle = RegisterEventSource(NULL, cf_syslog_ident);
300 		if (evtHandle == NULL || evtHandle == INVALID_HANDLE_VALUE) {
301 			evtHandle = INVALID_HANDLE_VALUE;
302 			return;
303 		}
304 	}
305 	ReportEvent(evtHandle, elevel, 0, 0, NULL, 1, 0, strlist, NULL);
306 }
307 
308 #endif
309